@backtest-kit/ui 6.14.0 → 6.16.0

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.
Files changed (41) hide show
  1. package/build/index.cjs +108 -14
  2. package/build/index.mjs +109 -15
  3. package/build/modules/frontend/build/3rdparty/qfchart_0.8.7/echarts.min.js +45 -0
  4. package/build/modules/frontend/build/3rdparty/qfchart_0.8.7/pinets.js +21534 -0
  5. package/build/modules/frontend/build/3rdparty/qfchart_0.8.7/qfchart.js +3997 -0
  6. package/build/modules/frontend/build/assets/Article-DYhWfFAi.js +1 -0
  7. package/build/modules/frontend/build/assets/Background-COpkJTP-.js +1 -0
  8. package/build/modules/frontend/build/assets/{IconPhoto-CNX0t203.js → IconPhoto-DUgsqsYP.js} +1 -1
  9. package/build/modules/frontend/build/assets/{KeyboardArrowLeft-5ogjRzwF.js → KeyboardArrowLeft-BlwcBPHn.js} +1 -1
  10. package/build/modules/frontend/build/assets/Refresh-aWduZclR.js +1 -0
  11. package/build/modules/frontend/build/assets/emitters-CSLNlyHG.js +1 -0
  12. package/build/modules/frontend/build/assets/hasRouteMatch-CImjW4Jf.js +1 -0
  13. package/build/modules/frontend/build/assets/{html2canvas-BnarC1H-.js → html2canvas-Do8zi4tS.js} +1 -1
  14. package/build/modules/frontend/build/assets/index-7wQuV9F_.js +1 -0
  15. package/build/modules/frontend/build/assets/index-B_46G-d8.js +1 -0
  16. package/build/modules/frontend/build/assets/index-BfiWEbdc.js +1 -0
  17. package/build/modules/frontend/build/assets/index-Bmqo1dsM.js +1 -0
  18. package/build/modules/frontend/build/assets/index-CETLG42q.js +1 -0
  19. package/build/modules/frontend/build/assets/index-Cb4CVVwH.js +1 -0
  20. package/build/modules/frontend/build/assets/{index-BMzg4t4L.js → index-Ce2sQITf.js} +7 -7
  21. package/build/modules/frontend/build/assets/{index-CYHbLqvS.js → index-CjymMd2D.js} +1 -1
  22. package/build/modules/frontend/build/assets/{index-DLH5bivm.js → index-D6YeBcAB.js} +1 -1
  23. package/build/modules/frontend/build/assets/index-WhalaOXd.js +1 -0
  24. package/build/modules/frontend/build/assets/{index-eYCKhqo-.js → index-kQ_oUmTQ.js} +1 -1
  25. package/build/modules/frontend/build/assets/{index-BY7xx8Qo.js → index-lZGMPvrT.js} +1 -1
  26. package/build/modules/frontend/build/assets/{index.es-XkghWdRJ.js → index.es-Cel98f_u.js} +1 -1
  27. package/build/modules/frontend/build/assets/{markdownit-CFUj-lOk.js → markdownit-22RgFULT.js} +1 -1
  28. package/build/modules/frontend/build/index.html +4 -1
  29. package/package.json +3 -3
  30. package/types.d.ts +11 -2
  31. package/build/modules/frontend/build/assets/Article-BFiJ1grS.js +0 -1
  32. package/build/modules/frontend/build/assets/Background-C4tJEFCD.js +0 -1
  33. package/build/modules/frontend/build/assets/Refresh-DPMpKmUf.js +0 -1
  34. package/build/modules/frontend/build/assets/emitters-BxM0853t.js +0 -1
  35. package/build/modules/frontend/build/assets/hasRouteMatch-DvMTRSzL.js +0 -1
  36. package/build/modules/frontend/build/assets/index-2cnF0R3L.js +0 -1
  37. package/build/modules/frontend/build/assets/index-B9jcFvZN.js +0 -1
  38. package/build/modules/frontend/build/assets/index-C-XjS3CF.js +0 -1
  39. package/build/modules/frontend/build/assets/index-C1SBiwRD.js +0 -1
  40. package/build/modules/frontend/build/assets/index-C5kEItX6.js +0 -1
  41. package/build/modules/frontend/build/assets/index-Dl9D9lt4.js +0 -1
@@ -0,0 +1,3997 @@
1
+
2
+ /*
3
+ * Copyright (C) 2025 Alaa-eddine KADDOURI
4
+ *
5
+ * Licensed under the Apache License, Version 2.0 (the "License");
6
+ * you may not use this file except in compliance with the License.
7
+ * You may obtain a copy of the License at
8
+ *
9
+ * http://www.apache.org/licenses/LICENSE-2.0
10
+ *
11
+ * Unless required by applicable law or agreed to in writing, software
12
+ * distributed under the License is distributed on an "AS IS" BASIS,
13
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14
+ * See the License for the specific language governing permissions and
15
+ * limitations under the License.
16
+ */
17
+ (function (global, factory) {
18
+ typeof exports === 'object' && typeof module !== 'undefined' ? factory(exports, require('echarts')) :
19
+ typeof define === 'function' && define.amd ? define(['exports', 'echarts'], factory) :
20
+ (global = typeof globalThis !== 'undefined' ? globalThis : global || self, factory(global.QFChart = {}, global.echarts));
21
+ })(this, (function (exports, echarts) { 'use strict';
22
+
23
+ function _interopNamespaceDefault(e) {
24
+ var n = Object.create(null);
25
+ if (e) {
26
+ Object.keys(e).forEach(function (k) {
27
+ if (k !== 'default') {
28
+ var d = Object.getOwnPropertyDescriptor(e, k);
29
+ Object.defineProperty(n, k, d.get ? d : {
30
+ enumerable: true,
31
+ get: function () { return e[k]; }
32
+ });
33
+ }
34
+ });
35
+ }
36
+ n.default = e;
37
+ return Object.freeze(n);
38
+ }
39
+
40
+ var echarts__namespace = /*#__PURE__*/_interopNamespaceDefault(echarts);
41
+
42
+ var __defProp$a = Object.defineProperty;
43
+ var __defNormalProp$a = (obj, key, value) => key in obj ? __defProp$a(obj, key, { enumerable: true, configurable: true, writable: true, value }) : obj[key] = value;
44
+ var __publicField$a = (obj, key, value) => {
45
+ __defNormalProp$a(obj, typeof key !== "symbol" ? key + "" : key, value);
46
+ return value;
47
+ };
48
+ class Indicator {
49
+ constructor(id, plots, paneIndex, options = {}) {
50
+ __publicField$a(this, "id");
51
+ __publicField$a(this, "plots");
52
+ __publicField$a(this, "paneIndex");
53
+ __publicField$a(this, "height");
54
+ __publicField$a(this, "collapsed");
55
+ __publicField$a(this, "titleColor");
56
+ __publicField$a(this, "controls");
57
+ this.id = id;
58
+ this.plots = plots;
59
+ this.paneIndex = paneIndex;
60
+ this.height = options.height;
61
+ this.collapsed = options.collapsed || false;
62
+ this.titleColor = options.titleColor;
63
+ this.controls = options.controls;
64
+ }
65
+ toggleCollapse() {
66
+ this.collapsed = !this.collapsed;
67
+ }
68
+ isVisible() {
69
+ return !this.collapsed;
70
+ }
71
+ /**
72
+ * Update indicator data incrementally by merging new points
73
+ *
74
+ * @param plots - New plots data to merge (same structure as constructor)
75
+ *
76
+ * @remarks
77
+ * This method merges new indicator data with existing data by timestamp.
78
+ * - New timestamps are added
79
+ * - Existing timestamps are updated with new values
80
+ * - All data is automatically sorted by time after merge
81
+ *
82
+ * **Important**: This method only updates the indicator's internal data structure.
83
+ * To see the changes reflected in the chart, you MUST call `chart.updateData()`
84
+ * after updating indicator data.
85
+ *
86
+ * **Usage Pattern**:
87
+ * ```typescript
88
+ * // 1. Update indicator data first
89
+ * indicator.updateData({
90
+ * macd: { data: [{ time: 1234567890, value: 150 }], options: { style: 'line', color: '#2962FF' } }
91
+ * });
92
+ *
93
+ * // 2. Then update chart data to trigger re-render
94
+ * chart.updateData([
95
+ * { time: 1234567890, open: 100, high: 105, low: 99, close: 103, volume: 1000 }
96
+ * ]);
97
+ * ```
98
+ *
99
+ * **Note**: If you update indicator data without corresponding market data changes,
100
+ * this typically indicates a recalculation scenario. In normal workflows, indicator
101
+ * values are derived from market data, so indicator updates should correspond to
102
+ * new or modified market bars.
103
+ */
104
+ updateData(plots) {
105
+ Object.keys(plots).forEach((plotName) => {
106
+ if (!this.plots[plotName]) {
107
+ this.plots[plotName] = plots[plotName];
108
+ } else {
109
+ const existingPlot = this.plots[plotName];
110
+ const newPlot = plots[plotName];
111
+ if (!existingPlot.data)
112
+ return;
113
+ if (newPlot.options) {
114
+ existingPlot.options = { ...existingPlot.options, ...newPlot.options };
115
+ }
116
+ const existingTimeMap = /* @__PURE__ */ new Map();
117
+ existingPlot.data?.forEach((point) => {
118
+ existingTimeMap.set(point.time, point);
119
+ });
120
+ newPlot.data?.forEach((point) => {
121
+ existingTimeMap.set(point.time, point);
122
+ });
123
+ existingPlot.data = Array.from(existingTimeMap.values()).sort((a, b) => a.time - b.time);
124
+ }
125
+ });
126
+ }
127
+ }
128
+
129
+ class AxisUtils {
130
+ // Create min/max functions that apply padding
131
+ static createMinFunction(paddingPercent) {
132
+ return (value) => {
133
+ const range = value.max - value.min;
134
+ const padding = range * (paddingPercent / 100);
135
+ return value.min - padding;
136
+ };
137
+ }
138
+ static createMaxFunction(paddingPercent) {
139
+ return (value) => {
140
+ const range = value.max - value.min;
141
+ const padding = range * (paddingPercent / 100);
142
+ return value.max + padding;
143
+ };
144
+ }
145
+ }
146
+
147
+ class LayoutManager {
148
+ static calculate(containerHeight, indicators, options, isMainCollapsed = false, maximizedPaneId = null, marketData) {
149
+ let pixelToPercent = 0;
150
+ if (containerHeight > 0) {
151
+ pixelToPercent = 1 / containerHeight * 100;
152
+ }
153
+ const yAxisPaddingPercent = options.yAxisPadding !== void 0 ? options.yAxisPadding : 5;
154
+ const separatePaneIndices = Array.from(indicators.values()).map((ind) => ind.paneIndex).filter((idx) => idx > 0).sort((a, b) => a - b).filter((value, index, self) => self.indexOf(value) === index);
155
+ const hasSeparatePane = separatePaneIndices.length > 0;
156
+ const dzVisible = options.dataZoom?.visible ?? true;
157
+ const dzPosition = options.dataZoom?.position ?? "top";
158
+ const dzHeight = options.dataZoom?.height ?? 6;
159
+ const dzStart = options.dataZoom?.start ?? 0;
160
+ const dzEnd = options.dataZoom?.end ?? 100;
161
+ let mainPaneTop = 8;
162
+ let chartAreaBottom = 92;
163
+ let maximizeTargetIndex = -1;
164
+ if (maximizedPaneId) {
165
+ if (maximizedPaneId === "main") {
166
+ maximizeTargetIndex = 0;
167
+ } else {
168
+ const ind = indicators.get(maximizedPaneId);
169
+ if (ind) {
170
+ maximizeTargetIndex = ind.paneIndex;
171
+ }
172
+ }
173
+ }
174
+ if (maximizeTargetIndex !== -1) {
175
+ const grid2 = [];
176
+ const xAxis2 = [];
177
+ const yAxis2 = [];
178
+ const dataZoom2 = [];
179
+ const dzStart2 = options.dataZoom?.start ?? 50;
180
+ const dzEnd2 = options.dataZoom?.end ?? 100;
181
+ const zoomOnTouch = options.dataZoom?.zoomOnTouch ?? true;
182
+ if (zoomOnTouch) {
183
+ dataZoom2.push({ type: "inside", xAxisIndex: "all", start: dzStart2, end: dzEnd2 });
184
+ }
185
+ const maxPaneIndex = hasSeparatePane ? Math.max(...separatePaneIndices) : 0;
186
+ const paneConfigs2 = [];
187
+ for (let i = 0; i <= maxPaneIndex; i++) {
188
+ const isTarget = i === maximizeTargetIndex;
189
+ grid2.push({
190
+ left: "10%",
191
+ right: "10%",
192
+ top: isTarget ? "5%" : "0%",
193
+ height: isTarget ? "90%" : "0%",
194
+ show: isTarget,
195
+ containLabel: false
196
+ });
197
+ xAxis2.push({
198
+ type: "category",
199
+ gridIndex: i,
200
+ data: [],
201
+ show: isTarget,
202
+ axisLabel: {
203
+ show: isTarget,
204
+ color: "#94a3b8",
205
+ fontFamily: options.fontFamily
206
+ },
207
+ axisLine: { show: isTarget, lineStyle: { color: "#334155" } },
208
+ splitLine: {
209
+ show: isTarget,
210
+ lineStyle: { color: "#334155", opacity: 0.5 }
211
+ }
212
+ });
213
+ let yMin;
214
+ let yMax;
215
+ if (i === 0 && maximizeTargetIndex === 0) {
216
+ yMin = options.yAxisMin !== void 0 && options.yAxisMin !== "auto" ? options.yAxisMin : AxisUtils.createMinFunction(yAxisPaddingPercent);
217
+ yMax = options.yAxisMax !== void 0 && options.yAxisMax !== "auto" ? options.yAxisMax : AxisUtils.createMaxFunction(yAxisPaddingPercent);
218
+ } else {
219
+ yMin = AxisUtils.createMinFunction(yAxisPaddingPercent);
220
+ yMax = AxisUtils.createMaxFunction(yAxisPaddingPercent);
221
+ }
222
+ yAxis2.push({
223
+ position: "right",
224
+ gridIndex: i,
225
+ show: isTarget,
226
+ scale: true,
227
+ min: yMin,
228
+ max: yMax,
229
+ axisLabel: {
230
+ show: isTarget,
231
+ color: "#94a3b8",
232
+ fontFamily: options.fontFamily,
233
+ formatter: (value) => {
234
+ if (options.yAxisLabelFormatter) {
235
+ return options.yAxisLabelFormatter(value);
236
+ }
237
+ const decimals = options.yAxisDecimalPlaces !== void 0 ? options.yAxisDecimalPlaces : 2;
238
+ if (typeof value === "number") {
239
+ return value.toFixed(decimals);
240
+ }
241
+ return String(value);
242
+ }
243
+ },
244
+ splitLine: {
245
+ show: isTarget,
246
+ lineStyle: { color: "#334155", opacity: 0.5 }
247
+ }
248
+ });
249
+ if (i > 0) {
250
+ const ind = Array.from(indicators.values()).find((ind2) => ind2.paneIndex === i);
251
+ if (ind) {
252
+ paneConfigs2.push({
253
+ index: i,
254
+ height: isTarget ? 90 : 0,
255
+ top: isTarget ? 5 : 0,
256
+ isCollapsed: false,
257
+ indicatorId: ind.id,
258
+ titleColor: ind.titleColor,
259
+ controls: ind.controls
260
+ });
261
+ }
262
+ }
263
+ }
264
+ return {
265
+ grid: grid2,
266
+ xAxis: xAxis2,
267
+ yAxis: yAxis2,
268
+ dataZoom: dataZoom2,
269
+ paneLayout: paneConfigs2,
270
+ mainPaneHeight: maximizeTargetIndex === 0 ? 90 : 0,
271
+ mainPaneTop: maximizeTargetIndex === 0 ? 5 : 0,
272
+ pixelToPercent,
273
+ overlayYAxisMap: /* @__PURE__ */ new Map(),
274
+ // No overlays in maximized view
275
+ separatePaneYAxisOffset: 1
276
+ // In maximized view, no overlays, so separate panes start at 1
277
+ };
278
+ }
279
+ if (dzVisible) {
280
+ if (dzPosition === "top") {
281
+ mainPaneTop = dzHeight + 4;
282
+ chartAreaBottom = 95;
283
+ } else {
284
+ chartAreaBottom = 100 - dzHeight - 2;
285
+ mainPaneTop = 8;
286
+ }
287
+ } else {
288
+ mainPaneTop = 5;
289
+ chartAreaBottom = 95;
290
+ }
291
+ let gapPercent = 5;
292
+ if (containerHeight > 0) {
293
+ gapPercent = 20 / containerHeight * 100;
294
+ }
295
+ let mainHeightVal = 75;
296
+ let paneConfigs = [];
297
+ if (hasSeparatePane) {
298
+ const panes = separatePaneIndices.map((idx) => {
299
+ const ind = Array.from(indicators.values()).find((i) => i.paneIndex === idx);
300
+ return {
301
+ index: idx,
302
+ requestedHeight: ind?.height,
303
+ isCollapsed: ind?.collapsed ?? false,
304
+ indicatorId: ind?.id,
305
+ titleColor: ind?.titleColor,
306
+ controls: ind?.controls
307
+ };
308
+ });
309
+ const resolvedPanes = panes.map((p) => ({
310
+ ...p,
311
+ height: p.isCollapsed ? 3 : p.requestedHeight !== void 0 ? p.requestedHeight : 15
312
+ }));
313
+ const totalIndicatorHeight = resolvedPanes.reduce((sum, p) => sum + p.height, 0);
314
+ const totalGaps = resolvedPanes.length * gapPercent;
315
+ const totalBottomSpace = totalIndicatorHeight + totalGaps;
316
+ const totalAvailable = chartAreaBottom - mainPaneTop;
317
+ mainHeightVal = totalAvailable - totalBottomSpace;
318
+ if (isMainCollapsed) {
319
+ mainHeightVal = 3;
320
+ } else {
321
+ if (mainHeightVal < 20) {
322
+ mainHeightVal = Math.max(mainHeightVal, 10);
323
+ }
324
+ }
325
+ let currentTop = mainPaneTop + mainHeightVal + gapPercent;
326
+ paneConfigs = resolvedPanes.map((p) => {
327
+ const config = {
328
+ index: p.index,
329
+ height: p.height,
330
+ top: currentTop,
331
+ isCollapsed: p.isCollapsed,
332
+ indicatorId: p.indicatorId,
333
+ titleColor: p.titleColor,
334
+ controls: p.controls
335
+ };
336
+ currentTop += p.height + gapPercent;
337
+ return config;
338
+ });
339
+ } else {
340
+ mainHeightVal = chartAreaBottom - mainPaneTop;
341
+ if (isMainCollapsed) {
342
+ mainHeightVal = 3;
343
+ }
344
+ }
345
+ const grid = [];
346
+ grid.push({
347
+ left: "10%",
348
+ right: "10%",
349
+ top: mainPaneTop + "%",
350
+ height: mainHeightVal + "%",
351
+ containLabel: false
352
+ // We handle margins explicitly
353
+ });
354
+ paneConfigs.forEach((pane) => {
355
+ grid.push({
356
+ left: "10%",
357
+ right: "10%",
358
+ top: pane.top + "%",
359
+ height: pane.height + "%",
360
+ containLabel: false
361
+ });
362
+ });
363
+ const allXAxisIndices = [0, ...paneConfigs.map((_, i) => i + 1)];
364
+ const xAxis = [];
365
+ const isMainBottom = paneConfigs.length === 0;
366
+ xAxis.push({
367
+ type: "category",
368
+ data: [],
369
+ // Will be filled by SeriesBuilder or QFChart
370
+ gridIndex: 0,
371
+ scale: true,
372
+ // boundaryGap will be set in QFChart.ts based on padding option
373
+ axisLine: {
374
+ onZero: false,
375
+ show: !isMainCollapsed,
376
+ lineStyle: { color: "#334155" }
377
+ },
378
+ splitLine: {
379
+ show: !isMainCollapsed,
380
+ lineStyle: { color: "#334155", opacity: 0.5 }
381
+ },
382
+ axisLabel: {
383
+ show: !isMainCollapsed,
384
+ color: "#94a3b8",
385
+ fontFamily: options.fontFamily || "sans-serif",
386
+ formatter: (value) => {
387
+ if (options.yAxisLabelFormatter) {
388
+ return options.yAxisLabelFormatter(value);
389
+ }
390
+ const decimals = options.yAxisDecimalPlaces !== void 0 ? options.yAxisDecimalPlaces : 2;
391
+ if (typeof value === "number") {
392
+ return value.toFixed(decimals);
393
+ }
394
+ return String(value);
395
+ }
396
+ },
397
+ axisTick: { show: !isMainCollapsed },
398
+ axisPointer: {
399
+ label: {
400
+ show: isMainBottom,
401
+ fontSize: 11,
402
+ backgroundColor: "#475569"
403
+ }
404
+ }
405
+ });
406
+ paneConfigs.forEach((pane, i) => {
407
+ const isBottom = i === paneConfigs.length - 1;
408
+ xAxis.push({
409
+ type: "category",
410
+ gridIndex: i + 1,
411
+ // 0 is main
412
+ data: [],
413
+ // Shared data
414
+ axisLabel: { show: false },
415
+ // Hide labels on indicator panes
416
+ axisLine: { show: !pane.isCollapsed, lineStyle: { color: "#334155" } },
417
+ axisTick: { show: false },
418
+ splitLine: { show: false },
419
+ axisPointer: {
420
+ label: {
421
+ show: isBottom,
422
+ fontSize: 11,
423
+ backgroundColor: "#475569"
424
+ }
425
+ }
426
+ });
427
+ });
428
+ const yAxis = [];
429
+ let mainYAxisMin;
430
+ let mainYAxisMax;
431
+ if (options.yAxisMin !== void 0 && options.yAxisMin !== "auto") {
432
+ mainYAxisMin = options.yAxisMin;
433
+ } else {
434
+ mainYAxisMin = AxisUtils.createMinFunction(yAxisPaddingPercent);
435
+ }
436
+ if (options.yAxisMax !== void 0 && options.yAxisMax !== "auto") {
437
+ mainYAxisMax = options.yAxisMax;
438
+ } else {
439
+ mainYAxisMax = AxisUtils.createMaxFunction(yAxisPaddingPercent);
440
+ }
441
+ yAxis.push({
442
+ position: "right",
443
+ scale: true,
444
+ min: mainYAxisMin,
445
+ max: mainYAxisMax,
446
+ gridIndex: 0,
447
+ splitLine: {
448
+ show: !isMainCollapsed,
449
+ lineStyle: { color: "#334155", opacity: 0.5 }
450
+ },
451
+ axisLine: { show: !isMainCollapsed, lineStyle: { color: "#334155" } },
452
+ axisLabel: {
453
+ show: !isMainCollapsed,
454
+ color: "#94a3b8",
455
+ fontFamily: options.fontFamily || "sans-serif",
456
+ formatter: (value) => {
457
+ if (options.yAxisLabelFormatter) {
458
+ return options.yAxisLabelFormatter(value);
459
+ }
460
+ const decimals = options.yAxisDecimalPlaces !== void 0 ? options.yAxisDecimalPlaces : 2;
461
+ if (typeof value === "number") {
462
+ return value.toFixed(decimals);
463
+ }
464
+ return String(value);
465
+ }
466
+ }
467
+ });
468
+ let nextYAxisIndex = 1;
469
+ let priceMin = -Infinity;
470
+ let priceMax = Infinity;
471
+ if (marketData && marketData.length > 0) {
472
+ priceMin = Math.min(...marketData.map((d) => d.low));
473
+ priceMax = Math.max(...marketData.map((d) => d.high));
474
+ }
475
+ const overlayYAxisMap = /* @__PURE__ */ new Map();
476
+ indicators.forEach((indicator, id) => {
477
+ if (indicator.paneIndex === 0 && !indicator.collapsed) {
478
+ if (marketData && marketData.length > 0) {
479
+ Object.entries(indicator.plots).forEach(([plotName, plot]) => {
480
+ const plotKey = `${id}::${plotName}`;
481
+ const visualOnlyStyles = ["background", "barcolor", "char"];
482
+ const isShapeWithPriceLocation = plot.options.style === "shape" && (plot.options.location === "abovebar" || plot.options.location === "belowbar");
483
+ if (visualOnlyStyles.includes(plot.options.style)) {
484
+ if (!overlayYAxisMap.has(plotKey)) {
485
+ overlayYAxisMap.set(plotKey, nextYAxisIndex);
486
+ nextYAxisIndex++;
487
+ }
488
+ return;
489
+ }
490
+ if (plot.options.style === "shape" && !isShapeWithPriceLocation) {
491
+ if (!overlayYAxisMap.has(plotKey)) {
492
+ overlayYAxisMap.set(plotKey, nextYAxisIndex);
493
+ nextYAxisIndex++;
494
+ }
495
+ return;
496
+ }
497
+ const values = [];
498
+ if (plot.data) {
499
+ Object.values(plot.data).forEach((value) => {
500
+ if (typeof value === "number" && !isNaN(value) && isFinite(value)) {
501
+ values.push(value);
502
+ }
503
+ });
504
+ }
505
+ if (values.length > 0) {
506
+ const plotMin = Math.min(...values);
507
+ const plotMax = Math.max(...values);
508
+ const plotRange = plotMax - plotMin;
509
+ const priceRange = priceMax - priceMin;
510
+ const isWithinBounds = plotMin >= priceMin * 0.5 && plotMax <= priceMax * 1.5;
511
+ const hasSimilarMagnitude = plotRange > priceRange * 0.01;
512
+ const isCompatible = isWithinBounds && hasSimilarMagnitude;
513
+ if (!isCompatible) {
514
+ if (!overlayYAxisMap.has(plotKey)) {
515
+ overlayYAxisMap.set(plotKey, nextYAxisIndex);
516
+ nextYAxisIndex++;
517
+ }
518
+ }
519
+ }
520
+ });
521
+ }
522
+ }
523
+ });
524
+ const numOverlayAxes = overlayYAxisMap.size > 0 ? nextYAxisIndex - 1 : 0;
525
+ for (let i = 0; i < numOverlayAxes; i++) {
526
+ yAxis.push({
527
+ position: "left",
528
+ scale: true,
529
+ min: AxisUtils.createMinFunction(yAxisPaddingPercent),
530
+ max: AxisUtils.createMaxFunction(yAxisPaddingPercent),
531
+ gridIndex: 0,
532
+ show: false,
533
+ // Hide the axis visual elements
534
+ splitLine: { show: false },
535
+ axisLine: { show: false },
536
+ axisLabel: { show: false }
537
+ });
538
+ }
539
+ const separatePaneYAxisOffset = nextYAxisIndex;
540
+ paneConfigs.forEach((pane, i) => {
541
+ yAxis.push({
542
+ position: "right",
543
+ scale: true,
544
+ min: AxisUtils.createMinFunction(yAxisPaddingPercent),
545
+ max: AxisUtils.createMaxFunction(yAxisPaddingPercent),
546
+ gridIndex: i + 1,
547
+ splitLine: {
548
+ show: !pane.isCollapsed,
549
+ lineStyle: { color: "#334155", opacity: 0.3 }
550
+ },
551
+ axisLabel: {
552
+ show: !pane.isCollapsed,
553
+ color: "#94a3b8",
554
+ fontFamily: options.fontFamily || "sans-serif",
555
+ fontSize: 10,
556
+ formatter: (value) => {
557
+ if (options.yAxisLabelFormatter) {
558
+ return options.yAxisLabelFormatter(value);
559
+ }
560
+ const decimals = options.yAxisDecimalPlaces !== void 0 ? options.yAxisDecimalPlaces : 2;
561
+ if (typeof value === "number") {
562
+ return value.toFixed(decimals);
563
+ }
564
+ return String(value);
565
+ }
566
+ },
567
+ axisLine: { show: !pane.isCollapsed, lineStyle: { color: "#334155" } }
568
+ });
569
+ });
570
+ const dataZoom = [];
571
+ if (dzVisible) {
572
+ const zoomOnTouch = options.dataZoom?.zoomOnTouch ?? true;
573
+ if (zoomOnTouch) {
574
+ dataZoom.push({
575
+ type: "inside",
576
+ xAxisIndex: allXAxisIndices,
577
+ start: dzStart,
578
+ end: dzEnd
579
+ });
580
+ }
581
+ if (dzPosition === "top") {
582
+ dataZoom.push({
583
+ type: "slider",
584
+ xAxisIndex: allXAxisIndices,
585
+ top: "1%",
586
+ height: dzHeight + "%",
587
+ start: dzStart,
588
+ end: dzEnd,
589
+ borderColor: "#334155",
590
+ textStyle: { color: "#cbd5e1" },
591
+ brushSelect: false
592
+ });
593
+ } else {
594
+ dataZoom.push({
595
+ type: "slider",
596
+ xAxisIndex: allXAxisIndices,
597
+ bottom: "1%",
598
+ height: dzHeight + "%",
599
+ start: dzStart,
600
+ end: dzEnd,
601
+ borderColor: "#334155",
602
+ textStyle: { color: "#cbd5e1" },
603
+ brushSelect: false
604
+ });
605
+ }
606
+ }
607
+ return {
608
+ grid,
609
+ xAxis,
610
+ yAxis,
611
+ dataZoom,
612
+ paneLayout: paneConfigs,
613
+ mainPaneHeight: mainHeightVal,
614
+ mainPaneTop,
615
+ pixelToPercent,
616
+ overlayYAxisMap,
617
+ separatePaneYAxisOffset
618
+ };
619
+ }
620
+ static calculateMaximized(containerHeight, options, targetPaneIndex) {
621
+ return {
622
+ grid: [],
623
+ xAxis: [],
624
+ yAxis: [],
625
+ dataZoom: [],
626
+ paneLayout: [],
627
+ mainPaneHeight: 0,
628
+ mainPaneTop: 0,
629
+ pixelToPercent: 0
630
+ };
631
+ }
632
+ }
633
+
634
+ class LineRenderer {
635
+ render(context) {
636
+ const { seriesName, xAxisIndex, yAxisIndex, dataArray, colorArray, plotOptions } = context;
637
+ const defaultColor = "#2962ff";
638
+ return {
639
+ name: seriesName,
640
+ type: "custom",
641
+ xAxisIndex,
642
+ yAxisIndex,
643
+ renderItem: (params, api) => {
644
+ const index = params.dataIndex;
645
+ if (index === 0)
646
+ return;
647
+ const y2 = api.value(1);
648
+ const y1 = api.value(2);
649
+ if (y2 === null || isNaN(y2) || y1 === null || isNaN(y1))
650
+ return;
651
+ const p1 = api.coord([index - 1, y1]);
652
+ const p2 = api.coord([index, y2]);
653
+ return {
654
+ type: "line",
655
+ shape: {
656
+ x1: p1[0],
657
+ y1: p1[1],
658
+ x2: p2[0],
659
+ y2: p2[1]
660
+ },
661
+ style: {
662
+ stroke: colorArray[index] || plotOptions.color || defaultColor,
663
+ lineWidth: plotOptions.linewidth || 1
664
+ },
665
+ silent: true
666
+ };
667
+ },
668
+ // Data format: [index, value, prevValue]
669
+ data: dataArray.map((val, i) => [i, val, i > 0 ? dataArray[i - 1] : null])
670
+ };
671
+ }
672
+ }
673
+
674
+ class StepRenderer {
675
+ render(context) {
676
+ const { seriesName, xAxisIndex, yAxisIndex, dataArray, colorArray, plotOptions } = context;
677
+ const defaultColor = "#2962ff";
678
+ return {
679
+ name: seriesName,
680
+ type: "custom",
681
+ xAxisIndex,
682
+ yAxisIndex,
683
+ renderItem: (params, api) => {
684
+ const x = api.value(0);
685
+ const y = api.value(1);
686
+ if (isNaN(y) || y === null)
687
+ return;
688
+ const coords = api.coord([x, y]);
689
+ const width = api.size([1, 0])[0];
690
+ return {
691
+ type: "line",
692
+ shape: {
693
+ x1: coords[0] - width / 2,
694
+ y1: coords[1],
695
+ x2: coords[0] + width / 2,
696
+ y2: coords[1]
697
+ },
698
+ style: {
699
+ stroke: colorArray[params.dataIndex] || plotOptions.color || defaultColor,
700
+ lineWidth: plotOptions.linewidth || 1
701
+ },
702
+ silent: true
703
+ };
704
+ },
705
+ data: dataArray.map((val, i) => [i, val])
706
+ };
707
+ }
708
+ }
709
+
710
+ class HistogramRenderer {
711
+ render(context) {
712
+ const { seriesName, xAxisIndex, yAxisIndex, dataArray, colorArray, plotOptions } = context;
713
+ const defaultColor = "#2962ff";
714
+ return {
715
+ name: seriesName,
716
+ type: "bar",
717
+ xAxisIndex,
718
+ yAxisIndex,
719
+ data: dataArray.map((val, i) => ({
720
+ value: val,
721
+ itemStyle: colorArray[i] ? { color: colorArray[i] } : void 0
722
+ })),
723
+ itemStyle: { color: plotOptions.color || defaultColor }
724
+ };
725
+ }
726
+ }
727
+
728
+ const imageCache = /* @__PURE__ */ new Map();
729
+ function textToBase64Image(text, color = "#00da3c", fontSize = "64px") {
730
+ if (typeof document === "undefined")
731
+ return "";
732
+ const cacheKey = `${text}-${color}-${fontSize}`;
733
+ if (imageCache.has(cacheKey)) {
734
+ return imageCache.get(cacheKey);
735
+ }
736
+ const canvas = document.createElement("canvas");
737
+ const ctx = canvas.getContext("2d");
738
+ canvas.width = 32;
739
+ canvas.height = 32;
740
+ if (ctx) {
741
+ ctx.font = "bold " + fontSize + " Arial";
742
+ ctx.fillStyle = color;
743
+ ctx.textAlign = "center";
744
+ ctx.textBaseline = "middle";
745
+ ctx.fillText(text, 16, 16);
746
+ const dataUrl = canvas.toDataURL("image/png");
747
+ imageCache.set(cacheKey, dataUrl);
748
+ return dataUrl;
749
+ }
750
+ return "";
751
+ }
752
+
753
+ class ScatterRenderer {
754
+ render(context) {
755
+ const { seriesName, xAxisIndex, yAxisIndex, dataArray, colorArray, plotOptions } = context;
756
+ const defaultColor = "#2962ff";
757
+ const style = plotOptions.style;
758
+ if (style === "char") {
759
+ return {
760
+ name: seriesName,
761
+ type: "scatter",
762
+ xAxisIndex,
763
+ yAxisIndex,
764
+ symbolSize: 0,
765
+ // Invisible
766
+ data: dataArray.map((val, i) => ({
767
+ value: [i, val],
768
+ itemStyle: { opacity: 0 }
769
+ })),
770
+ silent: true
771
+ // No interaction
772
+ };
773
+ }
774
+ const scatterData = dataArray.map((val, i) => {
775
+ if (val === null)
776
+ return null;
777
+ const pointColor = colorArray[i] || plotOptions.color || defaultColor;
778
+ const item = {
779
+ value: [i, val],
780
+ itemStyle: { color: pointColor }
781
+ };
782
+ if (style === "cross") {
783
+ item.symbol = `image://${textToBase64Image("+", pointColor, "24px")}`;
784
+ item.symbolSize = 16;
785
+ } else {
786
+ item.symbol = "circle";
787
+ item.symbolSize = 6;
788
+ }
789
+ return item;
790
+ }).filter((item) => item !== null);
791
+ return {
792
+ name: seriesName,
793
+ type: "scatter",
794
+ xAxisIndex,
795
+ yAxisIndex,
796
+ data: scatterData
797
+ };
798
+ }
799
+ }
800
+
801
+ class OHLCBarRenderer {
802
+ render(context) {
803
+ const { seriesName, xAxisIndex, yAxisIndex, dataArray, colorArray, optionsArray, plotOptions } = context;
804
+ const defaultColor = "#2962ff";
805
+ const isCandle = plotOptions.style === "candle";
806
+ const ohlcData = dataArray.map((val, i) => {
807
+ if (val === null || !Array.isArray(val) || val.length !== 4)
808
+ return null;
809
+ const [open, high, low, close] = val;
810
+ const pointOpts = optionsArray[i] || {};
811
+ const color = pointOpts.color || colorArray[i] || plotOptions.color || defaultColor;
812
+ const wickColor = pointOpts.wickcolor || plotOptions.wickcolor || color;
813
+ const borderColor = pointOpts.bordercolor || plotOptions.bordercolor || wickColor;
814
+ return [i, open, close, low, high, color, wickColor, borderColor];
815
+ }).filter((item) => item !== null);
816
+ return {
817
+ name: seriesName,
818
+ type: "custom",
819
+ xAxisIndex,
820
+ yAxisIndex,
821
+ renderItem: (params, api) => {
822
+ const xValue = api.value(0);
823
+ const openValue = api.value(1);
824
+ const closeValue = api.value(2);
825
+ const lowValue = api.value(3);
826
+ const highValue = api.value(4);
827
+ const color = api.value(5);
828
+ const wickColor = api.value(6);
829
+ const borderColor = api.value(7);
830
+ if (isNaN(openValue) || isNaN(closeValue) || isNaN(lowValue) || isNaN(highValue)) {
831
+ return null;
832
+ }
833
+ const xPos = api.coord([xValue, 0])[0];
834
+ const openPos = api.coord([xValue, openValue])[1];
835
+ const closePos = api.coord([xValue, closeValue])[1];
836
+ const lowPos = api.coord([xValue, lowValue])[1];
837
+ const highPos = api.coord([xValue, highValue])[1];
838
+ const barWidth = api.size([1, 0])[0] * 0.6;
839
+ if (isCandle) {
840
+ const bodyTop = Math.min(openPos, closePos);
841
+ const bodyBottom = Math.max(openPos, closePos);
842
+ const bodyHeight = Math.abs(closePos - openPos);
843
+ return {
844
+ type: "group",
845
+ children: [
846
+ // Upper wick
847
+ {
848
+ type: "line",
849
+ shape: {
850
+ x1: xPos,
851
+ y1: highPos,
852
+ x2: xPos,
853
+ y2: bodyTop
854
+ },
855
+ style: {
856
+ stroke: wickColor,
857
+ lineWidth: 1
858
+ }
859
+ },
860
+ // Lower wick
861
+ {
862
+ type: "line",
863
+ shape: {
864
+ x1: xPos,
865
+ y1: bodyBottom,
866
+ x2: xPos,
867
+ y2: lowPos
868
+ },
869
+ style: {
870
+ stroke: wickColor,
871
+ lineWidth: 1
872
+ }
873
+ },
874
+ // Body
875
+ {
876
+ type: "rect",
877
+ shape: {
878
+ x: xPos - barWidth / 2,
879
+ y: bodyTop,
880
+ width: barWidth,
881
+ height: bodyHeight || 1
882
+ // Minimum height for doji
883
+ },
884
+ style: {
885
+ fill: color,
886
+ stroke: borderColor,
887
+ lineWidth: 1
888
+ }
889
+ }
890
+ ]
891
+ };
892
+ } else {
893
+ const tickWidth = barWidth * 0.5;
894
+ return {
895
+ type: "group",
896
+ children: [
897
+ // Vertical line (low to high)
898
+ {
899
+ type: "line",
900
+ shape: {
901
+ x1: xPos,
902
+ y1: lowPos,
903
+ x2: xPos,
904
+ y2: highPos
905
+ },
906
+ style: {
907
+ stroke: color,
908
+ lineWidth: 1
909
+ }
910
+ },
911
+ // Open tick (left)
912
+ {
913
+ type: "line",
914
+ shape: {
915
+ x1: xPos - tickWidth,
916
+ y1: openPos,
917
+ x2: xPos,
918
+ y2: openPos
919
+ },
920
+ style: {
921
+ stroke: color,
922
+ lineWidth: 1
923
+ }
924
+ },
925
+ // Close tick (right)
926
+ {
927
+ type: "line",
928
+ shape: {
929
+ x1: xPos,
930
+ y1: closePos,
931
+ x2: xPos + tickWidth,
932
+ y2: closePos
933
+ },
934
+ style: {
935
+ stroke: color,
936
+ lineWidth: 1
937
+ }
938
+ }
939
+ ]
940
+ };
941
+ }
942
+ },
943
+ data: ohlcData
944
+ };
945
+ }
946
+ }
947
+
948
+ class ShapeUtils {
949
+ static getShapeSymbol(shape) {
950
+ switch (shape) {
951
+ case "arrowdown":
952
+ return "path://M12 24l-12-12h8v-12h8v12h8z";
953
+ case "arrowup":
954
+ return "path://M12 0l12 12h-8v12h-8v-12h-8z";
955
+ case "circle":
956
+ return "circle";
957
+ case "cross":
958
+ return "path://M11 2h2v9h9v2h-9v9h-2v-9h-9v-2h9z";
959
+ case "diamond":
960
+ return "diamond";
961
+ case "flag":
962
+ return "path://M6 2v20h2v-8h12l-2-6 2-6h-12z";
963
+ case "labeldown":
964
+ return "path://M4 2h16a2 2 0 0 1 2 2v12a2 2 0 0 1 -2 2h-6l-2 4l-2 -4h-6a2 2 0 0 1 -2 -2v-12a2 2 0 0 1 2 -2z";
965
+ case "labelup":
966
+ return "path://M12 2l2 4h6a2 2 0 0 1 2 2v12a2 2 0 0 1 -2 2h-16a2 2 0 0 1 -2 -2v-12a2 2 0 0 1 2 -2h6z";
967
+ case "square":
968
+ return "rect";
969
+ case "triangledown":
970
+ return "path://M12 21l-10-18h20z";
971
+ case "triangleup":
972
+ return "triangle";
973
+ case "xcross":
974
+ return "path://M19 6.41L17.59 5 12 10.59 6.41 5 5 6.41 10.59 12 5 17.59 6.41 19 12 13.41 17.59 19 19 17.59 13.41 12z";
975
+ default:
976
+ return "circle";
977
+ }
978
+ }
979
+ static getShapeRotation(shape) {
980
+ return 0;
981
+ }
982
+ static getShapeSize(size, width, height) {
983
+ if (width !== void 0 && height !== void 0) {
984
+ return [width, height];
985
+ }
986
+ let baseSize;
987
+ switch (size) {
988
+ case "tiny":
989
+ baseSize = 8;
990
+ break;
991
+ case "small":
992
+ baseSize = 12;
993
+ break;
994
+ case "normal":
995
+ case "auto":
996
+ baseSize = 16;
997
+ break;
998
+ case "large":
999
+ baseSize = 24;
1000
+ break;
1001
+ case "huge":
1002
+ baseSize = 32;
1003
+ break;
1004
+ default:
1005
+ baseSize = 16;
1006
+ }
1007
+ if (width !== void 0) {
1008
+ return [width, width];
1009
+ }
1010
+ if (height !== void 0) {
1011
+ return [height, height];
1012
+ }
1013
+ return baseSize;
1014
+ }
1015
+ // Helper to determine label position and distance relative to shape BASED ON LOCATION
1016
+ static getLabelConfig(shape, location) {
1017
+ switch (location) {
1018
+ case "abovebar":
1019
+ return { position: "top", distance: 5 };
1020
+ case "belowbar":
1021
+ return { position: "bottom", distance: 5 };
1022
+ case "top":
1023
+ return { position: "bottom", distance: 5 };
1024
+ case "bottom":
1025
+ return { position: "top", distance: 5 };
1026
+ case "absolute":
1027
+ default:
1028
+ if (shape === "labelup" || shape === "labeldown") {
1029
+ return { position: "inside", distance: 0 };
1030
+ }
1031
+ return { position: "top", distance: 5 };
1032
+ }
1033
+ }
1034
+ }
1035
+
1036
+ class ShapeRenderer {
1037
+ render(context) {
1038
+ const { seriesName, xAxisIndex, yAxisIndex, dataArray, colorArray, optionsArray, plotOptions, candlestickData } = context;
1039
+ const defaultColor = "#2962ff";
1040
+ const shapeData = dataArray.map((val, i) => {
1041
+ const pointOpts = optionsArray[i] || {};
1042
+ const globalOpts = plotOptions;
1043
+ const location = pointOpts.location || globalOpts.location || "absolute";
1044
+ if (location !== "absolute" && !val) {
1045
+ return null;
1046
+ }
1047
+ if (val === null || val === void 0) {
1048
+ return null;
1049
+ }
1050
+ const color = pointOpts.color || globalOpts.color || defaultColor;
1051
+ const shape = pointOpts.shape || globalOpts.shape || "circle";
1052
+ const size = pointOpts.size || globalOpts.size || "normal";
1053
+ const text = pointOpts.text || globalOpts.text;
1054
+ const textColor = pointOpts.textcolor || globalOpts.textcolor || "white";
1055
+ const width = pointOpts.width || globalOpts.width;
1056
+ const height = pointOpts.height || globalOpts.height;
1057
+ let yValue = val;
1058
+ let symbolOffset = [0, 0];
1059
+ if (location === "abovebar") {
1060
+ if (candlestickData && candlestickData[i]) {
1061
+ yValue = candlestickData[i].high;
1062
+ }
1063
+ symbolOffset = [0, "-150%"];
1064
+ } else if (location === "belowbar") {
1065
+ if (candlestickData && candlestickData[i]) {
1066
+ yValue = candlestickData[i].low;
1067
+ }
1068
+ symbolOffset = [0, "150%"];
1069
+ } else if (location === "top") {
1070
+ yValue = val;
1071
+ symbolOffset = [0, 0];
1072
+ } else if (location === "bottom") {
1073
+ yValue = val;
1074
+ symbolOffset = [0, 0];
1075
+ }
1076
+ const symbol = ShapeUtils.getShapeSymbol(shape);
1077
+ const symbolSize = ShapeUtils.getShapeSize(size, width, height);
1078
+ const rotate = ShapeUtils.getShapeRotation(shape);
1079
+ let finalSize = symbolSize;
1080
+ if (shape.includes("label")) {
1081
+ if (Array.isArray(symbolSize)) {
1082
+ finalSize = [symbolSize[0] * 2.5, symbolSize[1] * 2.5];
1083
+ } else {
1084
+ finalSize = symbolSize * 2.5;
1085
+ }
1086
+ }
1087
+ const labelConfig = ShapeUtils.getLabelConfig(shape, location);
1088
+ const item = {
1089
+ value: [i, yValue],
1090
+ symbol,
1091
+ symbolSize: finalSize,
1092
+ symbolRotate: rotate,
1093
+ symbolOffset,
1094
+ itemStyle: {
1095
+ color
1096
+ },
1097
+ label: {
1098
+ show: !!text,
1099
+ position: labelConfig.position,
1100
+ distance: labelConfig.distance,
1101
+ formatter: text,
1102
+ color: textColor,
1103
+ fontSize: 10,
1104
+ fontWeight: "bold"
1105
+ }
1106
+ };
1107
+ return item;
1108
+ }).filter((item) => item !== null);
1109
+ return {
1110
+ name: seriesName,
1111
+ type: "scatter",
1112
+ xAxisIndex,
1113
+ yAxisIndex,
1114
+ data: shapeData
1115
+ };
1116
+ }
1117
+ }
1118
+
1119
+ class BackgroundRenderer {
1120
+ render(context) {
1121
+ const { seriesName, xAxisIndex, yAxisIndex, dataArray, colorArray } = context;
1122
+ return {
1123
+ name: seriesName,
1124
+ type: "custom",
1125
+ xAxisIndex,
1126
+ yAxisIndex,
1127
+ z: -10,
1128
+ renderItem: (params, api) => {
1129
+ const xVal = api.value(0);
1130
+ if (isNaN(xVal))
1131
+ return;
1132
+ const start = api.coord([xVal, 0]);
1133
+ const size = api.size([1, 0]);
1134
+ const width = size[0];
1135
+ const sys = params.coordSys;
1136
+ const x = start[0] - width / 2;
1137
+ const barColor = colorArray[params.dataIndex];
1138
+ const val = api.value(1);
1139
+ if (!barColor || val === null || val === void 0 || isNaN(val))
1140
+ return;
1141
+ return {
1142
+ type: "rect",
1143
+ shape: {
1144
+ x,
1145
+ y: sys.y,
1146
+ width,
1147
+ height: sys.height
1148
+ },
1149
+ style: {
1150
+ fill: barColor,
1151
+ opacity: 0.3
1152
+ },
1153
+ silent: true
1154
+ };
1155
+ },
1156
+ data: dataArray.map((val, i) => [i, val])
1157
+ };
1158
+ }
1159
+ }
1160
+
1161
+ class ColorUtils {
1162
+ /**
1163
+ * Parse color string and extract opacity
1164
+ * Supports: hex (#RRGGBB), named colors (green, red), rgba(r,g,b,a), rgb(r,g,b)
1165
+ */
1166
+ static parseColor(colorStr) {
1167
+ if (!colorStr) {
1168
+ return { color: "#888888", opacity: 0.2 };
1169
+ }
1170
+ const rgbaMatch = colorStr.match(/rgba?\((\d+),\s*(\d+),\s*(\d+)(?:,\s*([\d.]+))?\)/);
1171
+ if (rgbaMatch) {
1172
+ const r = rgbaMatch[1];
1173
+ const g = rgbaMatch[2];
1174
+ const b = rgbaMatch[3];
1175
+ const a = rgbaMatch[4] ? parseFloat(rgbaMatch[4]) : 1;
1176
+ return {
1177
+ color: `rgb(${r},${g},${b})`,
1178
+ opacity: a
1179
+ };
1180
+ }
1181
+ return {
1182
+ color: colorStr,
1183
+ opacity: 0.3
1184
+ };
1185
+ }
1186
+ }
1187
+
1188
+ class FillRenderer {
1189
+ render(context) {
1190
+ const { seriesName, xAxisIndex, yAxisIndex, plotOptions, plotDataArrays, indicatorId, plotName } = context;
1191
+ const totalDataLength = context.dataArray.length;
1192
+ const plot1Key = plotOptions.plot1 ? `${indicatorId}::${plotOptions.plot1}` : null;
1193
+ const plot2Key = plotOptions.plot2 ? `${indicatorId}::${plotOptions.plot2}` : null;
1194
+ if (!plot1Key || !plot2Key) {
1195
+ console.warn(`Fill plot "${plotName}" missing plot1 or plot2 reference`);
1196
+ return null;
1197
+ }
1198
+ const plot1Data = plotDataArrays?.get(plot1Key);
1199
+ const plot2Data = plotDataArrays?.get(plot2Key);
1200
+ if (!plot1Data || !plot2Data) {
1201
+ console.warn(`Fill plot "${plotName}" references non-existent plots: ${plotOptions.plot1}, ${plotOptions.plot2}`);
1202
+ return null;
1203
+ }
1204
+ const { color: fillColor, opacity: fillOpacity } = ColorUtils.parseColor(plotOptions.color || "rgba(128, 128, 128, 0.2)");
1205
+ const fillDataWithPrev = [];
1206
+ for (let i = 0; i < totalDataLength; i++) {
1207
+ const y1 = plot1Data[i];
1208
+ const y2 = plot2Data[i];
1209
+ const prevY1 = i > 0 ? plot1Data[i - 1] : null;
1210
+ const prevY2 = i > 0 ? plot2Data[i - 1] : null;
1211
+ fillDataWithPrev.push([i, y1, y2, prevY1, prevY2]);
1212
+ }
1213
+ return {
1214
+ name: seriesName,
1215
+ type: "custom",
1216
+ xAxisIndex,
1217
+ yAxisIndex,
1218
+ z: -5,
1219
+ // Render behind lines but above background
1220
+ renderItem: (params, api) => {
1221
+ const index = params.dataIndex;
1222
+ if (index === 0)
1223
+ return null;
1224
+ const y1 = api.value(1);
1225
+ const y2 = api.value(2);
1226
+ const prevY1 = api.value(3);
1227
+ const prevY2 = api.value(4);
1228
+ if (y1 === null || y2 === null || prevY1 === null || prevY2 === null || isNaN(y1) || isNaN(y2) || isNaN(prevY1) || isNaN(prevY2)) {
1229
+ return null;
1230
+ }
1231
+ const p1Prev = api.coord([index - 1, prevY1]);
1232
+ const p1Curr = api.coord([index, y1]);
1233
+ const p2Curr = api.coord([index, y2]);
1234
+ const p2Prev = api.coord([index - 1, prevY2]);
1235
+ return {
1236
+ type: "polygon",
1237
+ shape: {
1238
+ points: [
1239
+ p1Prev,
1240
+ // Top-left
1241
+ p1Curr,
1242
+ // Top-right
1243
+ p2Curr,
1244
+ // Bottom-right
1245
+ p2Prev
1246
+ // Bottom-left
1247
+ ]
1248
+ },
1249
+ style: {
1250
+ fill: fillColor,
1251
+ opacity: fillOpacity
1252
+ },
1253
+ silent: true
1254
+ };
1255
+ },
1256
+ data: fillDataWithPrev
1257
+ };
1258
+ }
1259
+ }
1260
+
1261
+ var __defProp$9 = Object.defineProperty;
1262
+ var __defNormalProp$9 = (obj, key, value) => key in obj ? __defProp$9(obj, key, { enumerable: true, configurable: true, writable: true, value }) : obj[key] = value;
1263
+ var __publicField$9 = (obj, key, value) => {
1264
+ __defNormalProp$9(obj, typeof key !== "symbol" ? key + "" : key, value);
1265
+ return value;
1266
+ };
1267
+ const _SeriesRendererFactory = class _SeriesRendererFactory {
1268
+ static register(style, renderer) {
1269
+ this.renderers.set(style, renderer);
1270
+ }
1271
+ static get(style) {
1272
+ return this.renderers.get(style) || this.renderers.get("line");
1273
+ }
1274
+ };
1275
+ __publicField$9(_SeriesRendererFactory, "renderers", /* @__PURE__ */ new Map());
1276
+ _SeriesRendererFactory.register("line", new LineRenderer());
1277
+ _SeriesRendererFactory.register("step", new StepRenderer());
1278
+ _SeriesRendererFactory.register("histogram", new HistogramRenderer());
1279
+ _SeriesRendererFactory.register("columns", new HistogramRenderer());
1280
+ _SeriesRendererFactory.register("circles", new ScatterRenderer());
1281
+ _SeriesRendererFactory.register("cross", new ScatterRenderer());
1282
+ _SeriesRendererFactory.register("char", new ScatterRenderer());
1283
+ _SeriesRendererFactory.register("bar", new OHLCBarRenderer());
1284
+ _SeriesRendererFactory.register("candle", new OHLCBarRenderer());
1285
+ _SeriesRendererFactory.register("shape", new ShapeRenderer());
1286
+ _SeriesRendererFactory.register("background", new BackgroundRenderer());
1287
+ _SeriesRendererFactory.register("fill", new FillRenderer());
1288
+ let SeriesRendererFactory = _SeriesRendererFactory;
1289
+
1290
+ var __defProp$8 = Object.defineProperty;
1291
+ var __defNormalProp$8 = (obj, key, value) => key in obj ? __defProp$8(obj, key, { enumerable: true, configurable: true, writable: true, value }) : obj[key] = value;
1292
+ var __publicField$8 = (obj, key, value) => {
1293
+ __defNormalProp$8(obj, typeof key !== "symbol" ? key + "" : key, value);
1294
+ return value;
1295
+ };
1296
+ const _SeriesBuilder = class _SeriesBuilder {
1297
+ static buildCandlestickSeries(marketData, options, totalLength) {
1298
+ const upColor = options.upColor || "#00da3c";
1299
+ const downColor = options.downColor || "#ec0000";
1300
+ const data = marketData.map((d) => [d.open, d.close, d.low, d.high]);
1301
+ if (totalLength && totalLength > data.length) {
1302
+ const padding = totalLength - data.length;
1303
+ for (let i = 0; i < padding; i++) {
1304
+ data.push(null);
1305
+ }
1306
+ }
1307
+ let markLine = void 0;
1308
+ if (options.lastPriceLine?.visible !== false && marketData.length > 0) {
1309
+ const lastBar = marketData[marketData.length - 1];
1310
+ const lastClose = lastBar.close;
1311
+ const isUp = lastBar.close >= lastBar.open;
1312
+ const lineColor = options.lastPriceLine?.color || (isUp ? upColor : downColor);
1313
+ let lineStyleType = options.lastPriceLine?.lineStyle || "dashed";
1314
+ if (lineStyleType.startsWith("linestyle_")) {
1315
+ lineStyleType = lineStyleType.replace("linestyle_", "");
1316
+ }
1317
+ markLine = {
1318
+ symbol: ["none", "none"],
1319
+ data: [
1320
+ {
1321
+ yAxis: lastClose,
1322
+ label: {
1323
+ show: true,
1324
+ position: "end",
1325
+ // Right side
1326
+ formatter: (params) => {
1327
+ if (options.yAxisLabelFormatter) {
1328
+ return options.yAxisLabelFormatter(params.value);
1329
+ }
1330
+ const decimals = options.yAxisDecimalPlaces !== void 0 ? options.yAxisDecimalPlaces : 2;
1331
+ return typeof params.value === "number" ? params.value.toFixed(decimals) : params.value;
1332
+ },
1333
+ color: "#fff",
1334
+ backgroundColor: lineColor,
1335
+ padding: [2, 4],
1336
+ borderRadius: 2,
1337
+ fontSize: 11,
1338
+ fontWeight: "bold"
1339
+ },
1340
+ lineStyle: {
1341
+ color: lineColor,
1342
+ type: lineStyleType,
1343
+ width: 1,
1344
+ opacity: 0.8
1345
+ }
1346
+ }
1347
+ ],
1348
+ animation: false,
1349
+ silent: true
1350
+ // Disable interaction
1351
+ };
1352
+ }
1353
+ return {
1354
+ type: "candlestick",
1355
+ name: options.title || "Market",
1356
+ data,
1357
+ itemStyle: {
1358
+ color: upColor,
1359
+ color0: downColor,
1360
+ borderColor: upColor,
1361
+ borderColor0: downColor
1362
+ },
1363
+ markLine,
1364
+ xAxisIndex: 0,
1365
+ yAxisIndex: 0,
1366
+ z: 5
1367
+ };
1368
+ }
1369
+ static buildIndicatorSeries(indicators, timeToIndex, paneLayout, totalDataLength, dataIndexOffset = 0, candlestickData, overlayYAxisMap, separatePaneYAxisOffset = 1) {
1370
+ const series = [];
1371
+ const barColors = new Array(totalDataLength).fill(null);
1372
+ const plotDataArrays = /* @__PURE__ */ new Map();
1373
+ indicators.forEach((indicator, id) => {
1374
+ if (indicator.collapsed)
1375
+ return;
1376
+ const sortedPlots = Object.keys(indicator.plots).sort((a, b) => {
1377
+ const plotA = indicator.plots[a];
1378
+ const plotB = indicator.plots[b];
1379
+ const isFillA = plotA.options.style === "fill";
1380
+ const isFillB = plotB.options.style === "fill";
1381
+ if (isFillA && !isFillB)
1382
+ return 1;
1383
+ if (!isFillA && isFillB)
1384
+ return -1;
1385
+ return 0;
1386
+ });
1387
+ sortedPlots.forEach((plotName) => {
1388
+ const plot = indicator.plots[plotName];
1389
+ const seriesName = `${id}::${plotName}`;
1390
+ let xAxisIndex = 0;
1391
+ let yAxisIndex = 0;
1392
+ const plotOverlay = plot.options.overlay;
1393
+ const isPlotOverlay = plotOverlay !== void 0 ? plotOverlay : indicator.paneIndex === 0;
1394
+ if (isPlotOverlay) {
1395
+ xAxisIndex = 0;
1396
+ if (overlayYAxisMap && overlayYAxisMap.has(seriesName)) {
1397
+ yAxisIndex = overlayYAxisMap.get(seriesName);
1398
+ } else {
1399
+ yAxisIndex = 0;
1400
+ }
1401
+ } else {
1402
+ const confIndex = paneLayout.findIndex((p) => p.index === indicator.paneIndex);
1403
+ if (confIndex !== -1) {
1404
+ xAxisIndex = confIndex + 1;
1405
+ yAxisIndex = separatePaneYAxisOffset + confIndex;
1406
+ }
1407
+ }
1408
+ const dataArray = new Array(totalDataLength).fill(null);
1409
+ const colorArray = new Array(totalDataLength).fill(null);
1410
+ const optionsArray = new Array(totalDataLength).fill(null);
1411
+ plot.data?.forEach((point) => {
1412
+ const index = timeToIndex.get(point.time);
1413
+ if (index !== void 0) {
1414
+ const plotOffset = point.options?.offset ?? plot.options.offset ?? 0;
1415
+ const offsetIndex = index + dataIndexOffset + plotOffset;
1416
+ if (offsetIndex >= 0 && offsetIndex < totalDataLength) {
1417
+ let value = point.value;
1418
+ const pointColor = point.options?.color;
1419
+ const isNaColor = pointColor === null || pointColor === "na" || pointColor === "NaN" || typeof pointColor === "number" && isNaN(pointColor);
1420
+ if (isNaColor) {
1421
+ value = null;
1422
+ }
1423
+ dataArray[offsetIndex] = value;
1424
+ colorArray[offsetIndex] = pointColor || plot.options.color || _SeriesBuilder.DEFAULT_COLOR;
1425
+ optionsArray[offsetIndex] = point.options || {};
1426
+ }
1427
+ }
1428
+ });
1429
+ plotDataArrays.set(`${id}::${plotName}`, dataArray);
1430
+ if (plot.options?.style?.startsWith("style_")) {
1431
+ plot.options.style = plot.options.style.replace("style_", "");
1432
+ }
1433
+ if (plot.options.style === "barcolor") {
1434
+ plot.data?.forEach((point) => {
1435
+ const index = timeToIndex.get(point.time);
1436
+ if (index !== void 0) {
1437
+ const plotOffset = point.options?.offset ?? plot.options.offset ?? 0;
1438
+ const offsetIndex = index + dataIndexOffset + plotOffset;
1439
+ if (offsetIndex >= 0 && offsetIndex < totalDataLength) {
1440
+ const pointColor = point.options?.color || plot.options.color || _SeriesBuilder.DEFAULT_COLOR;
1441
+ const isNaColor = pointColor === null || pointColor === "na" || pointColor === "NaN" || typeof pointColor === "number" && isNaN(pointColor);
1442
+ if (!isNaColor && point.value !== null && point.value !== void 0) {
1443
+ barColors[offsetIndex] = pointColor;
1444
+ }
1445
+ }
1446
+ }
1447
+ });
1448
+ return;
1449
+ }
1450
+ const renderer = SeriesRendererFactory.get(plot.options.style);
1451
+ const seriesConfig = renderer.render({
1452
+ seriesName,
1453
+ xAxisIndex,
1454
+ yAxisIndex,
1455
+ dataArray,
1456
+ colorArray,
1457
+ optionsArray,
1458
+ plotOptions: plot.options,
1459
+ candlestickData,
1460
+ plotDataArrays,
1461
+ indicatorId: id,
1462
+ plotName
1463
+ });
1464
+ if (seriesConfig) {
1465
+ series.push(seriesConfig);
1466
+ }
1467
+ });
1468
+ });
1469
+ return { series, barColors };
1470
+ }
1471
+ };
1472
+ __publicField$8(_SeriesBuilder, "DEFAULT_COLOR", "#2962ff");
1473
+ let SeriesBuilder = _SeriesBuilder;
1474
+
1475
+ class GraphicBuilder {
1476
+ static build(layout, options, onToggle, isMainCollapsed = false, maximizedPaneId = null) {
1477
+ const graphic = [];
1478
+ const pixelToPercent = layout.pixelToPercent;
1479
+ const mainPaneTop = layout.mainPaneTop;
1480
+ const showMain = !maximizedPaneId || maximizedPaneId === "main";
1481
+ if (showMain) {
1482
+ const titleTopMargin = 10 * pixelToPercent;
1483
+ graphic.push({
1484
+ type: "text",
1485
+ left: "8.5%",
1486
+ top: mainPaneTop + titleTopMargin + "%",
1487
+ z: 10,
1488
+ style: {
1489
+ text: options.title || "Market",
1490
+ fill: options.titleColor || "#fff",
1491
+ font: `bold 16px ${options.fontFamily || "sans-serif"}`,
1492
+ textVerticalAlign: "top"
1493
+ }
1494
+ });
1495
+ if (options.watermark !== false) {
1496
+ const bottomY = layout.mainPaneTop + layout.mainPaneHeight;
1497
+ graphic.push({
1498
+ type: "text",
1499
+ right: "11%",
1500
+ top: bottomY - 3 + "%",
1501
+ // Position 5% from bottom of main chart
1502
+ z: 10,
1503
+ style: {
1504
+ text: "QFChart",
1505
+ fill: options.fontColor || "#cbd5e1",
1506
+ font: `bold 16px sans-serif`,
1507
+ opacity: 0.1
1508
+ },
1509
+ cursor: "pointer",
1510
+ onclick: () => {
1511
+ window.open("https://quantforge.org", "_blank");
1512
+ }
1513
+ });
1514
+ }
1515
+ const controls = [];
1516
+ if (options.controls?.collapse) {
1517
+ controls.push({
1518
+ type: "group",
1519
+ children: [
1520
+ {
1521
+ type: "rect",
1522
+ shape: { width: 20, height: 20, r: 2 },
1523
+ style: { fill: "#334155", stroke: "#475569", lineWidth: 1 },
1524
+ onclick: () => onToggle("main", "collapse")
1525
+ },
1526
+ {
1527
+ type: "text",
1528
+ style: {
1529
+ text: isMainCollapsed ? "+" : "\u2212",
1530
+ fill: "#cbd5e1",
1531
+ font: `bold 14px ${options.fontFamily}`,
1532
+ x: 10,
1533
+ y: 10,
1534
+ textAlign: "center",
1535
+ textVerticalAlign: "middle"
1536
+ },
1537
+ silent: true
1538
+ }
1539
+ ]
1540
+ });
1541
+ }
1542
+ if (options.controls?.maximize) {
1543
+ const isMaximized = maximizedPaneId === "main";
1544
+ const xOffset = options.controls?.collapse ? 25 : 0;
1545
+ controls.push({
1546
+ type: "group",
1547
+ x: xOffset,
1548
+ children: [
1549
+ {
1550
+ type: "rect",
1551
+ shape: { width: 20, height: 20, r: 2 },
1552
+ style: { fill: "#334155", stroke: "#475569", lineWidth: 1 },
1553
+ onclick: () => onToggle("main", "maximize")
1554
+ },
1555
+ {
1556
+ type: "text",
1557
+ style: {
1558
+ text: isMaximized ? "\u2750" : "\u25A1",
1559
+ // Simple chars for now
1560
+ fill: "#cbd5e1",
1561
+ font: `14px ${options.fontFamily}`,
1562
+ x: 10,
1563
+ y: 10,
1564
+ textAlign: "center",
1565
+ textVerticalAlign: "middle"
1566
+ },
1567
+ silent: true
1568
+ }
1569
+ ]
1570
+ });
1571
+ }
1572
+ if (options.controls?.fullscreen) {
1573
+ let xOffset = 0;
1574
+ if (options.controls?.collapse)
1575
+ xOffset += 25;
1576
+ if (options.controls?.maximize)
1577
+ xOffset += 25;
1578
+ controls.push({
1579
+ type: "group",
1580
+ x: xOffset,
1581
+ children: [
1582
+ {
1583
+ type: "rect",
1584
+ shape: { width: 20, height: 20, r: 2 },
1585
+ style: { fill: "#334155", stroke: "#475569", lineWidth: 1 },
1586
+ onclick: () => onToggle("main", "fullscreen")
1587
+ },
1588
+ {
1589
+ type: "text",
1590
+ style: {
1591
+ text: "\u26F6",
1592
+ fill: "#cbd5e1",
1593
+ font: `14px ${options.fontFamily}`,
1594
+ x: 10,
1595
+ y: 10,
1596
+ textAlign: "center",
1597
+ textVerticalAlign: "middle"
1598
+ },
1599
+ silent: true
1600
+ }
1601
+ ]
1602
+ });
1603
+ }
1604
+ if (controls.length > 0) {
1605
+ graphic.push({
1606
+ type: "group",
1607
+ right: "10.5%",
1608
+ top: mainPaneTop + "%",
1609
+ children: controls
1610
+ });
1611
+ }
1612
+ }
1613
+ layout.paneLayout.forEach((pane) => {
1614
+ if (maximizedPaneId && pane.indicatorId !== maximizedPaneId) {
1615
+ return;
1616
+ }
1617
+ graphic.push({
1618
+ type: "text",
1619
+ left: "8.5%",
1620
+ top: pane.top + 10 * pixelToPercent + "%",
1621
+ z: 10,
1622
+ style: {
1623
+ text: pane.indicatorId || "",
1624
+ fill: pane.titleColor || "#fff",
1625
+ font: `bold 12px ${options.fontFamily || "sans-serif"}`,
1626
+ textVerticalAlign: "top"
1627
+ }
1628
+ });
1629
+ const controls = [];
1630
+ if (pane.controls?.collapse) {
1631
+ controls.push({
1632
+ type: "group",
1633
+ children: [
1634
+ {
1635
+ type: "rect",
1636
+ shape: { width: 20, height: 20, r: 2 },
1637
+ style: { fill: "#334155", stroke: "#475569", lineWidth: 1 },
1638
+ onclick: () => pane.indicatorId && onToggle(pane.indicatorId, "collapse")
1639
+ },
1640
+ {
1641
+ type: "text",
1642
+ style: {
1643
+ text: pane.isCollapsed ? "+" : "\u2212",
1644
+ fill: "#cbd5e1",
1645
+ font: `bold 14px ${options.fontFamily}`,
1646
+ x: 10,
1647
+ y: 10,
1648
+ textAlign: "center",
1649
+ textVerticalAlign: "middle"
1650
+ },
1651
+ silent: true
1652
+ }
1653
+ ]
1654
+ });
1655
+ }
1656
+ if (pane.controls?.maximize) {
1657
+ const isMaximized = maximizedPaneId === pane.indicatorId;
1658
+ const xOffset = pane.controls?.collapse ? 25 : 0;
1659
+ controls.push({
1660
+ type: "group",
1661
+ x: xOffset,
1662
+ children: [
1663
+ {
1664
+ type: "rect",
1665
+ shape: { width: 20, height: 20, r: 2 },
1666
+ style: { fill: "#334155", stroke: "#475569", lineWidth: 1 },
1667
+ onclick: () => pane.indicatorId && onToggle(pane.indicatorId, "maximize")
1668
+ },
1669
+ {
1670
+ type: "text",
1671
+ style: {
1672
+ text: isMaximized ? "\u2750" : "\u25A1",
1673
+ fill: "#cbd5e1",
1674
+ font: `14px ${options.fontFamily}`,
1675
+ x: 10,
1676
+ y: 10,
1677
+ textAlign: "center",
1678
+ textVerticalAlign: "middle"
1679
+ },
1680
+ silent: true
1681
+ }
1682
+ ]
1683
+ });
1684
+ }
1685
+ if (controls.length > 0) {
1686
+ graphic.push({
1687
+ type: "group",
1688
+ right: "10.5%",
1689
+ top: pane.top + "%",
1690
+ children: controls
1691
+ });
1692
+ }
1693
+ });
1694
+ return graphic;
1695
+ }
1696
+ }
1697
+
1698
+ class TooltipFormatter {
1699
+ static format(params, options) {
1700
+ if (!params || params.length === 0)
1701
+ return "";
1702
+ const marketName = options.title || "Market";
1703
+ const upColor = options.upColor || "#00da3c";
1704
+ const downColor = options.downColor || "#ec0000";
1705
+ const fontFamily = options.fontFamily || "sans-serif";
1706
+ const date = params[0].axisValue;
1707
+ let html = `<div style="font-weight: bold; margin-bottom: 5px; color: #cbd5e1; font-family: ${fontFamily};">${date}</div>`;
1708
+ const marketSeries = params.find(
1709
+ (p) => p.seriesType === "candlestick"
1710
+ );
1711
+ const indicatorParams = params.filter(
1712
+ (p) => p.seriesType !== "candlestick"
1713
+ );
1714
+ if (marketSeries) {
1715
+ const [_, open, close, low, high] = marketSeries.value;
1716
+ const color = close >= open ? upColor : downColor;
1717
+ html += `
1718
+ <div style="margin-bottom: 8px; font-family: ${fontFamily};">
1719
+ <div style="display:flex; justify-content:space-between; color:${color}; font-weight:bold;">
1720
+ <span>${marketName}</span>
1721
+ </div>
1722
+ <div style="display: grid; grid-template-columns: auto auto; gap: 2px 15px; font-size: 0.9em; color: #cbd5e1;">
1723
+ <span>Open:</span> <span style="text-align: right; color: ${close >= open ? upColor : downColor}">${open}</span>
1724
+ <span>High:</span> <span style="text-align: right; color: ${upColor}">${high}</span>
1725
+ <span>Low:</span> <span style="text-align: right; color: ${downColor}">${low}</span>
1726
+ <span>Close:</span> <span style="text-align: right; color: ${close >= open ? upColor : downColor}">${close}</span>
1727
+ </div>
1728
+ </div>
1729
+ `;
1730
+ }
1731
+ if (indicatorParams.length > 0) {
1732
+ html += `<div style="border-top: 1px solid #334155; margin: 5px 0; padding-top: 5px;"></div>`;
1733
+ const indicators = {};
1734
+ indicatorParams.forEach((p) => {
1735
+ const parts = p.seriesName.split("::");
1736
+ const indId = parts.length > 1 ? parts[0] : "Unknown";
1737
+ const plotName = parts.length > 1 ? parts[1] : p.seriesName;
1738
+ if (!indicators[indId])
1739
+ indicators[indId] = [];
1740
+ indicators[indId].push({ ...p, displayName: plotName });
1741
+ });
1742
+ Object.keys(indicators).forEach((indId) => {
1743
+ html += `
1744
+ <div style="margin-top: 8px; font-family: ${fontFamily};">
1745
+ <div style="font-weight:bold; color: #fff; margin-bottom: 2px;">${indId}</div>
1746
+ `;
1747
+ indicators[indId].forEach((p) => {
1748
+ let val = p.value;
1749
+ if (Array.isArray(val)) {
1750
+ val = val[1];
1751
+ }
1752
+ if (val === null || val === void 0)
1753
+ return;
1754
+ const valStr = typeof val === "number" ? val.toLocaleString(void 0, { maximumFractionDigits: 4 }) : val;
1755
+ html += `
1756
+ <div style="display: flex; justify-content: space-between; align-items: center; margin-bottom: 2px; padding-left: 8px;">
1757
+ <div>${p.marker} <span style="color: #cbd5e1;">${p.displayName}</span></div>
1758
+ <div style="font-size: 10px; color: #fff;padding-left:10px;">${valStr}</div>
1759
+ </div>`;
1760
+ });
1761
+ html += `</div>`;
1762
+ });
1763
+ }
1764
+ return html;
1765
+ }
1766
+ }
1767
+
1768
+ var __defProp$7 = Object.defineProperty;
1769
+ var __defNormalProp$7 = (obj, key, value) => key in obj ? __defProp$7(obj, key, { enumerable: true, configurable: true, writable: true, value }) : obj[key] = value;
1770
+ var __publicField$7 = (obj, key, value) => {
1771
+ __defNormalProp$7(obj, typeof key !== "symbol" ? key + "" : key, value);
1772
+ return value;
1773
+ };
1774
+ class PluginManager {
1775
+ constructor(context, toolbarContainer) {
1776
+ __publicField$7(this, "plugins", /* @__PURE__ */ new Map());
1777
+ __publicField$7(this, "activePluginId", null);
1778
+ __publicField$7(this, "context");
1779
+ __publicField$7(this, "toolbarContainer");
1780
+ __publicField$7(this, "tooltipElement", null);
1781
+ __publicField$7(this, "hideTimeout", null);
1782
+ this.context = context;
1783
+ this.toolbarContainer = toolbarContainer;
1784
+ this.createTooltip();
1785
+ this.renderToolbar();
1786
+ }
1787
+ createTooltip() {
1788
+ this.tooltipElement = document.createElement("div");
1789
+ Object.assign(this.tooltipElement.style, {
1790
+ position: "fixed",
1791
+ display: "none",
1792
+ backgroundColor: "#1e293b",
1793
+ color: "#e2e8f0",
1794
+ padding: "6px 10px",
1795
+ borderRadius: "6px",
1796
+ fontSize: "13px",
1797
+ lineHeight: "1.4",
1798
+ fontWeight: "500",
1799
+ border: "1px solid #334155",
1800
+ zIndex: "9999",
1801
+ pointerEvents: "none",
1802
+ whiteSpace: "nowrap",
1803
+ boxShadow: "0 4px 6px -1px rgba(0, 0, 0, 0.3), 0 2px 4px -1px rgba(0, 0, 0, 0.15)",
1804
+ fontFamily: this.context.getOptions().fontFamily || "sans-serif",
1805
+ transition: "opacity 0.15s ease-in-out, transform 0.15s ease-in-out",
1806
+ opacity: "0",
1807
+ transform: "translateX(-5px)"
1808
+ });
1809
+ document.body.appendChild(this.tooltipElement);
1810
+ }
1811
+ destroy() {
1812
+ if (this.tooltipElement && this.tooltipElement.parentNode) {
1813
+ this.tooltipElement.parentNode.removeChild(this.tooltipElement);
1814
+ }
1815
+ this.tooltipElement = null;
1816
+ }
1817
+ showTooltip(target, text) {
1818
+ if (!this.tooltipElement)
1819
+ return;
1820
+ if (this.hideTimeout) {
1821
+ clearTimeout(this.hideTimeout);
1822
+ this.hideTimeout = null;
1823
+ }
1824
+ const rect = target.getBoundingClientRect();
1825
+ this.tooltipElement.textContent = text;
1826
+ this.tooltipElement.style.display = "block";
1827
+ const tooltipRect = this.tooltipElement.getBoundingClientRect();
1828
+ const top = rect.top + (rect.height - tooltipRect.height) / 2;
1829
+ const left = rect.right + 10;
1830
+ this.tooltipElement.style.top = `${top}px`;
1831
+ this.tooltipElement.style.left = `${left}px`;
1832
+ requestAnimationFrame(() => {
1833
+ if (this.tooltipElement) {
1834
+ this.tooltipElement.style.opacity = "1";
1835
+ this.tooltipElement.style.transform = "translateX(0)";
1836
+ }
1837
+ });
1838
+ }
1839
+ hideTooltip() {
1840
+ if (!this.tooltipElement)
1841
+ return;
1842
+ this.tooltipElement.style.opacity = "0";
1843
+ this.tooltipElement.style.transform = "translateX(-5px)";
1844
+ if (this.hideTimeout) {
1845
+ clearTimeout(this.hideTimeout);
1846
+ }
1847
+ this.hideTimeout = setTimeout(() => {
1848
+ if (this.tooltipElement) {
1849
+ this.tooltipElement.style.display = "none";
1850
+ }
1851
+ this.hideTimeout = null;
1852
+ }, 150);
1853
+ }
1854
+ register(plugin) {
1855
+ if (this.plugins.has(plugin.id)) {
1856
+ console.warn(`Plugin with id ${plugin.id} is already registered.`);
1857
+ return;
1858
+ }
1859
+ this.plugins.set(plugin.id, plugin);
1860
+ plugin.init(this.context);
1861
+ this.addButton(plugin);
1862
+ }
1863
+ unregister(pluginId) {
1864
+ const plugin = this.plugins.get(pluginId);
1865
+ if (plugin) {
1866
+ if (this.activePluginId === pluginId) {
1867
+ this.deactivatePlugin();
1868
+ }
1869
+ plugin.destroy?.();
1870
+ this.plugins.delete(pluginId);
1871
+ this.removeButton(pluginId);
1872
+ }
1873
+ }
1874
+ activatePlugin(pluginId) {
1875
+ if (this.activePluginId === pluginId) {
1876
+ this.deactivatePlugin();
1877
+ return;
1878
+ }
1879
+ if (this.activePluginId) {
1880
+ this.deactivatePlugin();
1881
+ }
1882
+ const plugin = this.plugins.get(pluginId);
1883
+ if (plugin) {
1884
+ this.activePluginId = pluginId;
1885
+ this.setButtonActive(pluginId, true);
1886
+ plugin.activate?.();
1887
+ }
1888
+ }
1889
+ deactivatePlugin() {
1890
+ if (this.activePluginId) {
1891
+ const plugin = this.plugins.get(this.activePluginId);
1892
+ plugin?.deactivate?.();
1893
+ this.setButtonActive(this.activePluginId, false);
1894
+ this.activePluginId = null;
1895
+ }
1896
+ }
1897
+ // --- UI Handling ---
1898
+ renderToolbar() {
1899
+ this.toolbarContainer.innerHTML = "";
1900
+ this.toolbarContainer.classList.add("qfchart-toolbar");
1901
+ this.toolbarContainer.style.display = "flex";
1902
+ this.toolbarContainer.style.flexDirection = "column";
1903
+ this.toolbarContainer.style.width = "40px";
1904
+ this.toolbarContainer.style.backgroundColor = this.context.getOptions().backgroundColor || "#1e293b";
1905
+ this.toolbarContainer.style.borderRight = "1px solid #334155";
1906
+ this.toolbarContainer.style.padding = "5px";
1907
+ this.toolbarContainer.style.boxSizing = "border-box";
1908
+ this.toolbarContainer.style.gap = "5px";
1909
+ this.toolbarContainer.style.flexShrink = "0";
1910
+ }
1911
+ addButton(plugin) {
1912
+ const btn = document.createElement("button");
1913
+ btn.id = `qfchart-plugin-btn-${plugin.id}`;
1914
+ btn.style.width = "30px";
1915
+ btn.style.height = "30px";
1916
+ btn.style.padding = "4px";
1917
+ btn.style.border = "1px solid transparent";
1918
+ btn.style.borderRadius = "4px";
1919
+ btn.style.backgroundColor = "transparent";
1920
+ btn.style.cursor = "pointer";
1921
+ btn.style.color = this.context.getOptions().fontColor || "#cbd5e1";
1922
+ btn.style.display = "flex";
1923
+ btn.style.alignItems = "center";
1924
+ btn.style.justifyContent = "center";
1925
+ if (plugin.icon) {
1926
+ btn.innerHTML = plugin.icon;
1927
+ } else {
1928
+ btn.innerText = (plugin.name || plugin.id).substring(0, 2).toUpperCase();
1929
+ }
1930
+ btn.addEventListener("mouseenter", () => {
1931
+ if (this.activePluginId !== plugin.id) {
1932
+ btn.style.backgroundColor = "rgba(255, 255, 255, 0.1)";
1933
+ }
1934
+ this.showTooltip(btn, plugin.name || plugin.id);
1935
+ });
1936
+ btn.addEventListener("mouseleave", () => {
1937
+ if (this.activePluginId !== plugin.id) {
1938
+ btn.style.backgroundColor = "transparent";
1939
+ }
1940
+ this.hideTooltip();
1941
+ });
1942
+ btn.onclick = () => this.activatePlugin(plugin.id);
1943
+ this.toolbarContainer.appendChild(btn);
1944
+ }
1945
+ removeButton(pluginId) {
1946
+ const btn = this.toolbarContainer.querySelector(`#qfchart-plugin-btn-${pluginId}`);
1947
+ if (btn) {
1948
+ btn.remove();
1949
+ }
1950
+ }
1951
+ setButtonActive(pluginId, active) {
1952
+ const btn = this.toolbarContainer.querySelector(`#qfchart-plugin-btn-${pluginId}`);
1953
+ if (btn) {
1954
+ if (active) {
1955
+ btn.style.backgroundColor = "#2563eb";
1956
+ btn.style.color = "#ffffff";
1957
+ } else {
1958
+ btn.style.backgroundColor = "transparent";
1959
+ btn.style.color = this.context.getOptions().fontColor || "#cbd5e1";
1960
+ }
1961
+ }
1962
+ }
1963
+ }
1964
+
1965
+ var __defProp$6 = Object.defineProperty;
1966
+ var __defNormalProp$6 = (obj, key, value) => key in obj ? __defProp$6(obj, key, { enumerable: true, configurable: true, writable: true, value }) : obj[key] = value;
1967
+ var __publicField$6 = (obj, key, value) => {
1968
+ __defNormalProp$6(obj, typeof key !== "symbol" ? key + "" : key, value);
1969
+ return value;
1970
+ };
1971
+ class DrawingEditor {
1972
+ constructor(context) {
1973
+ __publicField$6(this, "context");
1974
+ __publicField$6(this, "isEditing", false);
1975
+ __publicField$6(this, "currentDrawing", null);
1976
+ __publicField$6(this, "editingPointIndex", null);
1977
+ __publicField$6(this, "zr");
1978
+ // Temporary ZRender elements for visual feedback during drag
1979
+ __publicField$6(this, "editGroup", null);
1980
+ __publicField$6(this, "editLine", null);
1981
+ __publicField$6(this, "editStartPoint", null);
1982
+ __publicField$6(this, "editEndPoint", null);
1983
+ __publicField$6(this, "isMovingShape", false);
1984
+ __publicField$6(this, "dragStart", null);
1985
+ __publicField$6(this, "initialPixelPoints", []);
1986
+ __publicField$6(this, "onDrawingMouseDown", (payload) => {
1987
+ if (this.isEditing)
1988
+ return;
1989
+ const drawing = this.context.getDrawing(payload.id);
1990
+ if (!drawing)
1991
+ return;
1992
+ this.isEditing = true;
1993
+ this.isMovingShape = true;
1994
+ this.currentDrawing = JSON.parse(JSON.stringify(drawing));
1995
+ this.dragStart = { x: payload.x, y: payload.y };
1996
+ this.initialPixelPoints = drawing.points.map((p) => {
1997
+ const pixel = this.context.coordinateConversion.dataToPixel(p);
1998
+ return pixel ? { x: pixel.x, y: pixel.y } : { x: 0, y: 0 };
1999
+ });
2000
+ this.context.lockChart();
2001
+ this.createEditGraphic();
2002
+ this.zr.on("mousemove", this.onMouseMove);
2003
+ this.zr.on("mouseup", this.onMouseUp);
2004
+ });
2005
+ __publicField$6(this, "onPointMouseDown", (payload) => {
2006
+ if (this.isEditing)
2007
+ return;
2008
+ const drawing = this.context.getDrawing(payload.id);
2009
+ if (!drawing)
2010
+ return;
2011
+ this.isEditing = true;
2012
+ this.currentDrawing = JSON.parse(JSON.stringify(drawing));
2013
+ this.editingPointIndex = payload.pointIndex;
2014
+ this.context.lockChart();
2015
+ this.createEditGraphic();
2016
+ this.zr.on("mousemove", this.onMouseMove);
2017
+ this.zr.on("mouseup", this.onMouseUp);
2018
+ });
2019
+ __publicField$6(this, "onMouseMove", (e) => {
2020
+ if (!this.isEditing || !this.currentDrawing)
2021
+ return;
2022
+ const x = e.offsetX;
2023
+ const y = e.offsetY;
2024
+ if (this.isMovingShape && this.dragStart) {
2025
+ const dx = x - this.dragStart.x;
2026
+ const dy = y - this.dragStart.y;
2027
+ const newP1 = {
2028
+ x: this.initialPixelPoints[0].x + dx,
2029
+ y: this.initialPixelPoints[0].y + dy
2030
+ };
2031
+ const newP2 = {
2032
+ x: this.initialPixelPoints[1].x + dx,
2033
+ y: this.initialPixelPoints[1].y + dy
2034
+ };
2035
+ this.editLine.setShape({
2036
+ x1: newP1.x,
2037
+ y1: newP1.y,
2038
+ x2: newP2.x,
2039
+ y2: newP2.y
2040
+ });
2041
+ this.editStartPoint.setShape({ cx: newP1.x, cy: newP1.y });
2042
+ this.editEndPoint.setShape({ cx: newP2.x, cy: newP2.y });
2043
+ } else if (this.editingPointIndex !== null) {
2044
+ if (this.editingPointIndex === 0) {
2045
+ this.editLine.setShape({ x1: x, y1: y });
2046
+ this.editStartPoint.setShape({ cx: x, cy: y });
2047
+ } else {
2048
+ this.editLine.setShape({ x2: x, y2: y });
2049
+ this.editEndPoint.setShape({ cx: x, cy: y });
2050
+ }
2051
+ }
2052
+ });
2053
+ __publicField$6(this, "onMouseUp", (e) => {
2054
+ if (!this.isEditing)
2055
+ return;
2056
+ this.finishEditing(e.offsetX, e.offsetY);
2057
+ });
2058
+ this.context = context;
2059
+ this.zr = this.context.getChart().getZr();
2060
+ this.bindEvents();
2061
+ }
2062
+ bindEvents() {
2063
+ this.context.events.on("drawing:point:mousedown", this.onPointMouseDown);
2064
+ this.context.events.on("drawing:mousedown", this.onDrawingMouseDown);
2065
+ }
2066
+ createEditGraphic() {
2067
+ if (!this.currentDrawing)
2068
+ return;
2069
+ this.editGroup = new echarts__namespace.graphic.Group();
2070
+ const p1Data = this.currentDrawing.points[0];
2071
+ const p2Data = this.currentDrawing.points[1];
2072
+ const p1 = this.context.coordinateConversion.dataToPixel(p1Data);
2073
+ const p2 = this.context.coordinateConversion.dataToPixel(p2Data);
2074
+ if (!p1 || !p2)
2075
+ return;
2076
+ this.editLine = new echarts__namespace.graphic.Line({
2077
+ shape: { x1: p1.x, y1: p1.y, x2: p2.x, y2: p2.y },
2078
+ style: {
2079
+ stroke: this.currentDrawing.style?.color || "#3b82f6",
2080
+ lineWidth: this.currentDrawing.style?.lineWidth || 2,
2081
+ lineDash: [4, 4]
2082
+ // Dashed to indicate editing
2083
+ },
2084
+ silent: true
2085
+ // Events pass through to handlers
2086
+ });
2087
+ this.editStartPoint = new echarts__namespace.graphic.Circle({
2088
+ shape: { cx: p1.x, cy: p1.y, r: 5 },
2089
+ style: { fill: "#fff", stroke: "#3b82f6", lineWidth: 2 },
2090
+ z: 1e3
2091
+ });
2092
+ this.editEndPoint = new echarts__namespace.graphic.Circle({
2093
+ shape: { cx: p2.x, cy: p2.y, r: 5 },
2094
+ style: { fill: "#fff", stroke: "#3b82f6", lineWidth: 2 },
2095
+ z: 1e3
2096
+ });
2097
+ this.editGroup.add(this.editLine);
2098
+ this.editGroup.add(this.editStartPoint);
2099
+ this.editGroup.add(this.editEndPoint);
2100
+ this.zr.add(this.editGroup);
2101
+ }
2102
+ finishEditing(finalX, finishY) {
2103
+ if (!this.currentDrawing)
2104
+ return;
2105
+ if (this.isMovingShape && this.dragStart) {
2106
+ const dx = finalX - this.dragStart.x;
2107
+ const dy = finishY - this.dragStart.y;
2108
+ const newPoints = this.initialPixelPoints.map((p, i) => {
2109
+ const newX = p.x + dx;
2110
+ const newY = p.y + dy;
2111
+ return this.context.coordinateConversion.pixelToData({
2112
+ x: newX,
2113
+ y: newY
2114
+ });
2115
+ });
2116
+ if (newPoints.every((p) => p !== null)) {
2117
+ if (newPoints[0] && newPoints[1]) {
2118
+ this.currentDrawing.points[0] = newPoints[0];
2119
+ this.currentDrawing.points[1] = newPoints[1];
2120
+ if (newPoints[0].paneIndex !== void 0) {
2121
+ this.currentDrawing.paneIndex = newPoints[0].paneIndex;
2122
+ }
2123
+ this.context.updateDrawing(this.currentDrawing);
2124
+ }
2125
+ }
2126
+ } else if (this.editingPointIndex !== null) {
2127
+ const newData = this.context.coordinateConversion.pixelToData({
2128
+ x: finalX,
2129
+ y: finishY
2130
+ });
2131
+ if (newData) {
2132
+ this.currentDrawing.points[this.editingPointIndex] = newData;
2133
+ if (this.editingPointIndex === 0 && newData.paneIndex !== void 0) {
2134
+ this.currentDrawing.paneIndex = newData.paneIndex;
2135
+ }
2136
+ this.context.updateDrawing(this.currentDrawing);
2137
+ }
2138
+ }
2139
+ this.isEditing = false;
2140
+ this.isMovingShape = false;
2141
+ this.dragStart = null;
2142
+ this.initialPixelPoints = [];
2143
+ this.currentDrawing = null;
2144
+ this.editingPointIndex = null;
2145
+ if (this.editGroup) {
2146
+ this.zr.remove(this.editGroup);
2147
+ this.editGroup = null;
2148
+ }
2149
+ this.zr.off("mousemove", this.onMouseMove);
2150
+ this.zr.off("mouseup", this.onMouseUp);
2151
+ this.context.unlockChart();
2152
+ }
2153
+ }
2154
+
2155
+ var __defProp$5 = Object.defineProperty;
2156
+ var __defNormalProp$5 = (obj, key, value) => key in obj ? __defProp$5(obj, key, { enumerable: true, configurable: true, writable: true, value }) : obj[key] = value;
2157
+ var __publicField$5 = (obj, key, value) => {
2158
+ __defNormalProp$5(obj, typeof key !== "symbol" ? key + "" : key, value);
2159
+ return value;
2160
+ };
2161
+ class EventBus {
2162
+ constructor() {
2163
+ __publicField$5(this, "handlers", /* @__PURE__ */ new Map());
2164
+ }
2165
+ on(event, handler) {
2166
+ if (!this.handlers.has(event)) {
2167
+ this.handlers.set(event, /* @__PURE__ */ new Set());
2168
+ }
2169
+ this.handlers.get(event).add(handler);
2170
+ }
2171
+ off(event, handler) {
2172
+ const handlers = this.handlers.get(event);
2173
+ if (handlers) {
2174
+ handlers.delete(handler);
2175
+ }
2176
+ }
2177
+ emit(event, payload) {
2178
+ const handlers = this.handlers.get(event);
2179
+ if (handlers) {
2180
+ handlers.forEach((handler) => {
2181
+ try {
2182
+ handler(payload);
2183
+ } catch (e) {
2184
+ console.error(`Error in EventBus handler for ${event}:`, e);
2185
+ }
2186
+ });
2187
+ }
2188
+ }
2189
+ clear() {
2190
+ this.handlers.clear();
2191
+ }
2192
+ }
2193
+
2194
+ var __defProp$4 = Object.defineProperty;
2195
+ var __defNormalProp$4 = (obj, key, value) => key in obj ? __defProp$4(obj, key, { enumerable: true, configurable: true, writable: true, value }) : obj[key] = value;
2196
+ var __publicField$4 = (obj, key, value) => {
2197
+ __defNormalProp$4(obj, typeof key !== "symbol" ? key + "" : key, value);
2198
+ return value;
2199
+ };
2200
+ class QFChart {
2201
+ constructor(container, options = {}) {
2202
+ __publicField$4(this, "chart");
2203
+ __publicField$4(this, "options");
2204
+ __publicField$4(this, "marketData", []);
2205
+ __publicField$4(this, "indicators", /* @__PURE__ */ new Map());
2206
+ __publicField$4(this, "timeToIndex", /* @__PURE__ */ new Map());
2207
+ __publicField$4(this, "pluginManager");
2208
+ __publicField$4(this, "drawingEditor");
2209
+ __publicField$4(this, "events", new EventBus());
2210
+ __publicField$4(this, "isMainCollapsed", false);
2211
+ __publicField$4(this, "maximizedPaneId", null);
2212
+ __publicField$4(this, "countdownInterval", null);
2213
+ __publicField$4(this, "selectedDrawingId", null);
2214
+ // Track selected drawing
2215
+ // Drawing System
2216
+ __publicField$4(this, "drawings", []);
2217
+ __publicField$4(this, "coordinateConversion", {
2218
+ pixelToData: (point) => {
2219
+ const option = this.chart.getOption();
2220
+ if (!option || !option.grid)
2221
+ return null;
2222
+ const gridCount = option.grid.length;
2223
+ for (let i = 0; i < gridCount; i++) {
2224
+ if (this.chart.containPixel({ gridIndex: i }, [point.x, point.y])) {
2225
+ this.chart.convertFromPixel({ seriesIndex: i }, [point.x, point.y]);
2226
+ const pGrid = this.chart.convertFromPixel({ gridIndex: i }, [point.x, point.y]);
2227
+ if (pGrid) {
2228
+ return { timeIndex: Math.round(pGrid[0]), value: pGrid[1], paneIndex: i };
2229
+ }
2230
+ }
2231
+ }
2232
+ return null;
2233
+ },
2234
+ dataToPixel: (point) => {
2235
+ const paneIdx = point.paneIndex || 0;
2236
+ const p = this.chart.convertToPixel({ gridIndex: paneIdx }, [point.timeIndex, point.value]);
2237
+ if (p) {
2238
+ return { x: p[0], y: p[1] };
2239
+ }
2240
+ return null;
2241
+ }
2242
+ });
2243
+ // Default colors and constants
2244
+ __publicField$4(this, "upColor", "#00da3c");
2245
+ __publicField$4(this, "downColor", "#ec0000");
2246
+ __publicField$4(this, "defaultPadding", 0);
2247
+ __publicField$4(this, "padding");
2248
+ __publicField$4(this, "dataIndexOffset", 0);
2249
+ // Offset for phantom padding data
2250
+ // DOM Elements for Layout
2251
+ __publicField$4(this, "rootContainer");
2252
+ __publicField$4(this, "layoutContainer");
2253
+ __publicField$4(this, "toolbarContainer");
2254
+ // New Toolbar
2255
+ __publicField$4(this, "leftSidebar");
2256
+ __publicField$4(this, "rightSidebar");
2257
+ __publicField$4(this, "chartContainer");
2258
+ __publicField$4(this, "onKeyDown", (e) => {
2259
+ if ((e.key === "Delete" || e.key === "Backspace") && this.selectedDrawingId) {
2260
+ this.removeDrawing(this.selectedDrawingId);
2261
+ this.selectedDrawingId = null;
2262
+ this.render();
2263
+ }
2264
+ });
2265
+ __publicField$4(this, "onFullscreenChange", () => {
2266
+ this.render();
2267
+ });
2268
+ // --- Interaction Locking ---
2269
+ __publicField$4(this, "isLocked", false);
2270
+ __publicField$4(this, "lockedState", null);
2271
+ this.rootContainer = container;
2272
+ this.options = {
2273
+ title: "Market",
2274
+ height: "600px",
2275
+ backgroundColor: "#1e293b",
2276
+ upColor: "#00da3c",
2277
+ downColor: "#ec0000",
2278
+ fontColor: "#cbd5e1",
2279
+ fontFamily: "sans-serif",
2280
+ padding: 0.01,
2281
+ dataZoom: {
2282
+ visible: true,
2283
+ position: "top",
2284
+ height: 6
2285
+ },
2286
+ layout: {
2287
+ mainPaneHeight: "50%",
2288
+ gap: 13
2289
+ },
2290
+ watermark: true,
2291
+ ...options
2292
+ };
2293
+ if (this.options.upColor)
2294
+ this.upColor = this.options.upColor;
2295
+ if (this.options.downColor)
2296
+ this.downColor = this.options.downColor;
2297
+ this.padding = this.options.padding !== void 0 ? this.options.padding : this.defaultPadding;
2298
+ if (this.options.height) {
2299
+ if (typeof this.options.height === "number") {
2300
+ this.rootContainer.style.height = `${this.options.height}px`;
2301
+ } else {
2302
+ this.rootContainer.style.height = this.options.height;
2303
+ }
2304
+ }
2305
+ this.rootContainer.innerHTML = "";
2306
+ this.layoutContainer = document.createElement("div");
2307
+ this.layoutContainer.style.display = "flex";
2308
+ this.layoutContainer.style.width = "100%";
2309
+ this.layoutContainer.style.height = "100%";
2310
+ this.layoutContainer.style.overflow = "hidden";
2311
+ this.rootContainer.appendChild(this.layoutContainer);
2312
+ this.leftSidebar = document.createElement("div");
2313
+ this.leftSidebar.style.display = "none";
2314
+ this.leftSidebar.style.width = "250px";
2315
+ this.leftSidebar.style.flexShrink = "0";
2316
+ this.leftSidebar.style.overflowY = "auto";
2317
+ this.leftSidebar.style.backgroundColor = this.options.backgroundColor || "#1e293b";
2318
+ this.leftSidebar.style.borderRight = "1px solid #334155";
2319
+ this.leftSidebar.style.padding = "10px";
2320
+ this.leftSidebar.style.boxSizing = "border-box";
2321
+ this.leftSidebar.style.color = "#cbd5e1";
2322
+ this.leftSidebar.style.fontSize = "12px";
2323
+ this.leftSidebar.style.fontFamily = this.options.fontFamily || "sans-serif";
2324
+ this.layoutContainer.appendChild(this.leftSidebar);
2325
+ this.toolbarContainer = document.createElement("div");
2326
+ this.layoutContainer.appendChild(this.toolbarContainer);
2327
+ this.chartContainer = document.createElement("div");
2328
+ this.chartContainer.style.flexGrow = "1";
2329
+ this.chartContainer.style.height = "100%";
2330
+ this.chartContainer.style.overflow = "hidden";
2331
+ this.layoutContainer.appendChild(this.chartContainer);
2332
+ this.rightSidebar = document.createElement("div");
2333
+ this.rightSidebar.style.display = "none";
2334
+ this.rightSidebar.style.width = "250px";
2335
+ this.rightSidebar.style.flexShrink = "0";
2336
+ this.rightSidebar.style.overflowY = "auto";
2337
+ this.rightSidebar.style.backgroundColor = this.options.backgroundColor || "#1e293b";
2338
+ this.rightSidebar.style.borderLeft = "1px solid #334155";
2339
+ this.rightSidebar.style.padding = "10px";
2340
+ this.rightSidebar.style.boxSizing = "border-box";
2341
+ this.rightSidebar.style.color = "#cbd5e1";
2342
+ this.rightSidebar.style.fontSize = "12px";
2343
+ this.rightSidebar.style.fontFamily = this.options.fontFamily || "sans-serif";
2344
+ this.layoutContainer.appendChild(this.rightSidebar);
2345
+ this.chart = echarts__namespace.init(this.chartContainer);
2346
+ this.pluginManager = new PluginManager(this, this.toolbarContainer);
2347
+ this.drawingEditor = new DrawingEditor(this);
2348
+ this.chart.on("dataZoom", (params) => {
2349
+ this.events.emit("chart:dataZoom", params);
2350
+ const triggerOn = this.options.databox?.triggerOn;
2351
+ const position = this.options.databox?.position;
2352
+ if (triggerOn === "click" && position === "floating") {
2353
+ this.chart.dispatchAction({
2354
+ type: "hideTip"
2355
+ });
2356
+ }
2357
+ });
2358
+ this.chart.on("finished", (params) => this.events.emit("chart:updated", params));
2359
+ this.chart.getZr().on("mousedown", (params) => this.events.emit("mouse:down", params));
2360
+ this.chart.getZr().on("mousemove", (params) => this.events.emit("mouse:move", params));
2361
+ this.chart.getZr().on("mouseup", (params) => this.events.emit("mouse:up", params));
2362
+ this.chart.getZr().on("click", (params) => this.events.emit("mouse:click", params));
2363
+ const zr = this.chart.getZr();
2364
+ const originalSetCursorStyle = zr.setCursorStyle;
2365
+ zr.setCursorStyle = function(cursorStyle) {
2366
+ if (cursorStyle === "grab") {
2367
+ cursorStyle = "crosshair";
2368
+ }
2369
+ originalSetCursorStyle.call(this, cursorStyle);
2370
+ };
2371
+ this.bindDrawingEvents();
2372
+ window.addEventListener("resize", this.resize.bind(this));
2373
+ document.addEventListener("fullscreenchange", this.onFullscreenChange);
2374
+ document.addEventListener("keydown", this.onKeyDown);
2375
+ }
2376
+ bindDrawingEvents() {
2377
+ let hideTimeout = null;
2378
+ const getDrawingInfo = (params) => {
2379
+ if (!params || params.componentType !== "series" || !params.seriesName?.startsWith("drawings")) {
2380
+ return null;
2381
+ }
2382
+ params.seriesIndex;
2383
+ const match = params.seriesName.match(/drawings-pane-(\d+)/);
2384
+ if (!match)
2385
+ return null;
2386
+ const paneIdx = parseInt(match[1]);
2387
+ const paneDrawings = this.drawings.filter((d) => (d.paneIndex || 0) === paneIdx);
2388
+ const drawing = paneDrawings[params.dataIndex];
2389
+ if (!drawing)
2390
+ return null;
2391
+ const targetName = params.event?.target?.name;
2392
+ return { drawing, targetName, paneIdx };
2393
+ };
2394
+ this.chart.on("mouseover", (params) => {
2395
+ const info = getDrawingInfo(params);
2396
+ if (!info)
2397
+ return;
2398
+ const group = params.event?.target?.parent;
2399
+ if (group) {
2400
+ const isSelected = info.drawing.id === this.selectedDrawingId;
2401
+ if (hideTimeout) {
2402
+ clearTimeout(hideTimeout);
2403
+ hideTimeout = null;
2404
+ }
2405
+ if (!isSelected) {
2406
+ group.children().forEach((child) => {
2407
+ if (child.name && child.name.startsWith("point")) {
2408
+ child.attr("style", { opacity: 1 });
2409
+ }
2410
+ });
2411
+ }
2412
+ }
2413
+ if (info.targetName === "line") {
2414
+ this.events.emit("drawing:hover", {
2415
+ id: info.drawing.id,
2416
+ type: info.drawing.type
2417
+ });
2418
+ this.chart.getZr().setCursorStyle("move");
2419
+ } else if (info.targetName?.startsWith("point")) {
2420
+ const pointIdx = info.targetName === "point-start" ? 0 : 1;
2421
+ this.events.emit("drawing:point:hover", {
2422
+ id: info.drawing.id,
2423
+ pointIndex: pointIdx
2424
+ });
2425
+ this.chart.getZr().setCursorStyle("pointer");
2426
+ }
2427
+ });
2428
+ this.chart.on("mouseout", (params) => {
2429
+ const info = getDrawingInfo(params);
2430
+ if (!info)
2431
+ return;
2432
+ const group = params.event?.target?.parent;
2433
+ if (info.drawing.id === this.selectedDrawingId) {
2434
+ return;
2435
+ }
2436
+ hideTimeout = setTimeout(() => {
2437
+ if (group) {
2438
+ if (this.selectedDrawingId === info.drawing.id)
2439
+ return;
2440
+ group.children().forEach((child) => {
2441
+ if (child.name && child.name.startsWith("point")) {
2442
+ child.attr("style", { opacity: 0 });
2443
+ }
2444
+ });
2445
+ }
2446
+ }, 50);
2447
+ if (info.targetName === "line") {
2448
+ this.events.emit("drawing:mouseout", { id: info.drawing.id });
2449
+ } else if (info.targetName?.startsWith("point")) {
2450
+ const pointIdx = info.targetName === "point-start" ? 0 : 1;
2451
+ this.events.emit("drawing:point:mouseout", {
2452
+ id: info.drawing.id,
2453
+ pointIndex: pointIdx
2454
+ });
2455
+ }
2456
+ this.chart.getZr().setCursorStyle("default");
2457
+ });
2458
+ this.chart.on("mousedown", (params) => {
2459
+ const info = getDrawingInfo(params);
2460
+ if (!info)
2461
+ return;
2462
+ const event = params.event?.event || params.event;
2463
+ const x = event?.offsetX;
2464
+ const y = event?.offsetY;
2465
+ if (info.targetName === "line") {
2466
+ this.events.emit("drawing:mousedown", {
2467
+ id: info.drawing.id,
2468
+ x,
2469
+ y
2470
+ });
2471
+ } else if (info.targetName?.startsWith("point")) {
2472
+ const pointIdx = info.targetName === "point-start" ? 0 : 1;
2473
+ this.events.emit("drawing:point:mousedown", {
2474
+ id: info.drawing.id,
2475
+ pointIndex: pointIdx,
2476
+ x,
2477
+ y
2478
+ });
2479
+ }
2480
+ });
2481
+ this.chart.on("click", (params) => {
2482
+ const info = getDrawingInfo(params);
2483
+ if (!info)
2484
+ return;
2485
+ if (this.selectedDrawingId !== info.drawing.id) {
2486
+ this.selectedDrawingId = info.drawing.id;
2487
+ this.events.emit("drawing:selected", { id: info.drawing.id });
2488
+ this.render();
2489
+ }
2490
+ if (info.targetName === "line") {
2491
+ this.events.emit("drawing:click", { id: info.drawing.id });
2492
+ } else if (info.targetName?.startsWith("point")) {
2493
+ const pointIdx = info.targetName === "point-start" ? 0 : 1;
2494
+ this.events.emit("drawing:point:click", {
2495
+ id: info.drawing.id,
2496
+ pointIndex: pointIdx
2497
+ });
2498
+ }
2499
+ });
2500
+ this.chart.getZr().on("click", (params) => {
2501
+ if (!params.target) {
2502
+ if (this.selectedDrawingId) {
2503
+ this.events.emit("drawing:deselected", { id: this.selectedDrawingId });
2504
+ this.selectedDrawingId = null;
2505
+ this.render();
2506
+ }
2507
+ }
2508
+ });
2509
+ }
2510
+ // --- Plugin System Integration ---
2511
+ getChart() {
2512
+ return this.chart;
2513
+ }
2514
+ getMarketData() {
2515
+ return this.marketData;
2516
+ }
2517
+ getTimeToIndex() {
2518
+ return this.timeToIndex;
2519
+ }
2520
+ getOptions() {
2521
+ return this.options;
2522
+ }
2523
+ disableTools() {
2524
+ this.pluginManager.deactivatePlugin();
2525
+ }
2526
+ registerPlugin(plugin) {
2527
+ this.pluginManager.register(plugin);
2528
+ }
2529
+ // --- Drawing System ---
2530
+ addDrawing(drawing) {
2531
+ this.drawings.push(drawing);
2532
+ this.render();
2533
+ }
2534
+ removeDrawing(id) {
2535
+ const index = this.drawings.findIndex((d) => d.id === id);
2536
+ if (index !== -1) {
2537
+ const drawing = this.drawings[index];
2538
+ this.drawings.splice(index, 1);
2539
+ this.events.emit("drawing:deleted", { id: drawing.id });
2540
+ this.render();
2541
+ }
2542
+ }
2543
+ getDrawing(id) {
2544
+ return this.drawings.find((d) => d.id === id);
2545
+ }
2546
+ updateDrawing(drawing) {
2547
+ const index = this.drawings.findIndex((d) => d.id === drawing.id);
2548
+ if (index !== -1) {
2549
+ this.drawings[index] = drawing;
2550
+ this.render();
2551
+ }
2552
+ }
2553
+ lockChart() {
2554
+ if (this.isLocked)
2555
+ return;
2556
+ this.isLocked = true;
2557
+ const option = this.chart.getOption();
2558
+ this.chart.setOption({
2559
+ dataZoom: option.dataZoom.map((dz) => ({ ...dz, disabled: true })),
2560
+ tooltip: { show: false }
2561
+ // Hide tooltip during drag
2562
+ // We can also disable series interaction if needed, but custom series is handled by us.
2563
+ });
2564
+ }
2565
+ unlockChart() {
2566
+ if (!this.isLocked)
2567
+ return;
2568
+ this.isLocked = false;
2569
+ const option = this.chart.getOption();
2570
+ const dzConfig = this.options.dataZoom || {};
2571
+ dzConfig.visible ?? true;
2572
+ if (option.dataZoom) {
2573
+ this.chart.setOption({
2574
+ dataZoom: option.dataZoom.map((dz) => ({
2575
+ ...dz,
2576
+ disabled: false
2577
+ })),
2578
+ tooltip: { show: true }
2579
+ });
2580
+ }
2581
+ }
2582
+ // --------------------------------
2583
+ setZoom(start, end) {
2584
+ this.chart.dispatchAction({
2585
+ type: "dataZoom",
2586
+ start,
2587
+ end
2588
+ });
2589
+ }
2590
+ setMarketData(data) {
2591
+ this.marketData = data;
2592
+ this.rebuildTimeIndex();
2593
+ this.render();
2594
+ }
2595
+ /**
2596
+ * Update market data incrementally without full re-render
2597
+ * Merges new/updated OHLCV data with existing data by timestamp
2598
+ *
2599
+ * @param data - Array of OHLCV data to merge
2600
+ *
2601
+ * @remarks
2602
+ * **Performance Optimization**: This method only triggers a chart update if the data array contains
2603
+ * new or modified bars. If an empty array is passed, no update occurs (expected behavior).
2604
+ *
2605
+ * **Usage Pattern for Updating Indicators**:
2606
+ * When updating both market data and indicators, follow this order:
2607
+ *
2608
+ * 1. Update indicator data first using `indicator.updateData(plots)`
2609
+ * 2. Then call `chart.updateData(newBars)` with the new/modified market data
2610
+ *
2611
+ * The chart update will trigger a re-render that includes the updated indicator data.
2612
+ *
2613
+ * **Important**: If you update indicator data without updating market data (e.g., recalculation
2614
+ * with same bars), you must still call `chart.updateData([...])` with at least one bar
2615
+ * to trigger the re-render. Calling with an empty array will NOT trigger an update.
2616
+ *
2617
+ * @example
2618
+ * ```typescript
2619
+ * // Step 1: Update indicator data
2620
+ * macdIndicator.updateData({
2621
+ * macd: { data: [{ time: 1234567890, value: 150 }], options: { style: 'line', color: '#2962FF' } }
2622
+ * });
2623
+ *
2624
+ * // Step 2: Update market data (triggers re-render with new indicator data)
2625
+ * chart.updateData([
2626
+ * { time: 1234567890, open: 100, high: 105, low: 99, close: 103, volume: 1000 }
2627
+ * ]);
2628
+ * ```
2629
+ *
2630
+ * @example
2631
+ * ```typescript
2632
+ * // If only updating existing bar (e.g., real-time tick updates):
2633
+ * const lastBar = { ...existingBar, close: newPrice, high: Math.max(existingBar.high, newPrice) };
2634
+ * chart.updateData([lastBar]); // Updates by timestamp
2635
+ * ```
2636
+ */
2637
+ updateData(data) {
2638
+ if (data.length === 0)
2639
+ return;
2640
+ const existingTimeMap = /* @__PURE__ */ new Map();
2641
+ this.marketData.forEach((bar) => {
2642
+ existingTimeMap.set(bar.time, bar);
2643
+ });
2644
+ data.forEach((bar) => {
2645
+ if (!existingTimeMap.has(bar.time)) ;
2646
+ existingTimeMap.set(bar.time, bar);
2647
+ });
2648
+ this.marketData = Array.from(existingTimeMap.values()).sort((a, b) => a.time - b.time);
2649
+ this.rebuildTimeIndex();
2650
+ const paddingPoints = this.dataIndexOffset;
2651
+ const candlestickSeries = SeriesBuilder.buildCandlestickSeries(this.marketData, this.options);
2652
+ const emptyCandle = { value: [NaN, NaN, NaN, NaN], itemStyle: { opacity: 0 } };
2653
+ const paddedCandlestickData = [
2654
+ ...Array(paddingPoints).fill(emptyCandle),
2655
+ ...candlestickSeries.data,
2656
+ ...Array(paddingPoints).fill(emptyCandle)
2657
+ ];
2658
+ const categoryData = [
2659
+ ...Array(paddingPoints).fill(""),
2660
+ ...this.marketData.map((k) => new Date(k.time).toLocaleString()),
2661
+ ...Array(paddingPoints).fill("")
2662
+ ];
2663
+ const currentOption = this.chart.getOption();
2664
+ const layout = LayoutManager.calculate(
2665
+ this.chart.getHeight(),
2666
+ this.indicators,
2667
+ this.options,
2668
+ this.isMainCollapsed,
2669
+ this.maximizedPaneId,
2670
+ this.marketData
2671
+ );
2672
+ const paddedOHLCVForShapes = [...Array(paddingPoints).fill(null), ...this.marketData, ...Array(paddingPoints).fill(null)];
2673
+ const { series: indicatorSeries, barColors } = SeriesBuilder.buildIndicatorSeries(
2674
+ this.indicators,
2675
+ this.timeToIndex,
2676
+ layout.paneLayout,
2677
+ categoryData.length,
2678
+ paddingPoints,
2679
+ paddedOHLCVForShapes,
2680
+ // Pass padded OHLCV data
2681
+ layout.overlayYAxisMap,
2682
+ // Pass overlay Y-axis mapping
2683
+ layout.separatePaneYAxisOffset
2684
+ // Pass Y-axis offset for separate panes
2685
+ );
2686
+ const coloredCandlestickData = paddedCandlestickData.map((candle, i) => {
2687
+ if (barColors[i]) {
2688
+ return {
2689
+ value: candle.value || candle,
2690
+ itemStyle: {
2691
+ color: barColors[i],
2692
+ color0: barColors[i],
2693
+ borderColor: barColors[i],
2694
+ borderColor0: barColors[i]
2695
+ }
2696
+ };
2697
+ }
2698
+ return candle;
2699
+ });
2700
+ const updateOption = {
2701
+ xAxis: currentOption.xAxis.map((axis, index) => ({
2702
+ data: categoryData
2703
+ })),
2704
+ series: [
2705
+ {
2706
+ data: coloredCandlestickData,
2707
+ markLine: candlestickSeries.markLine
2708
+ // Ensure markLine is updated
2709
+ },
2710
+ ...indicatorSeries.map((s) => {
2711
+ const update = { data: s.data };
2712
+ if (s.renderItem) {
2713
+ update.renderItem = s.renderItem;
2714
+ }
2715
+ return update;
2716
+ })
2717
+ ]
2718
+ };
2719
+ this.chart.setOption(updateOption, { notMerge: false });
2720
+ this.startCountdown();
2721
+ }
2722
+ startCountdown() {
2723
+ this.stopCountdown();
2724
+ if (!this.options.lastPriceLine?.showCountdown || !this.options.interval || this.marketData.length === 0) {
2725
+ return;
2726
+ }
2727
+ const updateLabel = () => {
2728
+ if (this.marketData.length === 0)
2729
+ return;
2730
+ const lastBar = this.marketData[this.marketData.length - 1];
2731
+ const nextCloseTime = lastBar.time + (this.options.interval || 0);
2732
+ const now = Date.now();
2733
+ const diff = nextCloseTime - now;
2734
+ if (diff <= 0) {
2735
+ return;
2736
+ }
2737
+ const absDiff = Math.abs(diff);
2738
+ const hours = Math.floor(absDiff / 36e5);
2739
+ const minutes = Math.floor(absDiff % 36e5 / 6e4);
2740
+ const seconds = Math.floor(absDiff % 6e4 / 1e3);
2741
+ const timeString = `${hours > 0 ? hours.toString().padStart(2, "0") + ":" : ""}${minutes.toString().padStart(2, "0")}:${seconds.toString().padStart(2, "0")}`;
2742
+ const currentOption = this.chart.getOption();
2743
+ if (!currentOption || !currentOption.series)
2744
+ return;
2745
+ const candleSeriesIndex = currentOption.series.findIndex((s) => s.type === "candlestick");
2746
+ if (candleSeriesIndex === -1)
2747
+ return;
2748
+ const candleSeries = currentOption.series[candleSeriesIndex];
2749
+ if (!candleSeries.markLine || !candleSeries.markLine.data || !candleSeries.markLine.data[0])
2750
+ return;
2751
+ const markLineData = candleSeries.markLine.data[0];
2752
+ markLineData.label.formatter;
2753
+ const price = markLineData.yAxis;
2754
+ let priceStr = "";
2755
+ if (this.options.yAxisLabelFormatter) {
2756
+ priceStr = this.options.yAxisLabelFormatter(price);
2757
+ } else {
2758
+ const decimals = this.options.yAxisDecimalPlaces !== void 0 ? this.options.yAxisDecimalPlaces : 2;
2759
+ priceStr = typeof price === "number" ? price.toFixed(decimals) : price;
2760
+ }
2761
+ const labelText = `${priceStr}
2762
+ ${timeString}`;
2763
+ this.chart.setOption({
2764
+ series: [
2765
+ {
2766
+ name: this.options.title || "Market",
2767
+ markLine: {
2768
+ data: [
2769
+ {
2770
+ ...markLineData,
2771
+ // Preserve lineStyle (color), symbol, yAxis, etc.
2772
+ label: {
2773
+ ...markLineData.label,
2774
+ // Preserve existing label styles including backgroundColor
2775
+ formatter: labelText
2776
+ // Update only the text
2777
+ }
2778
+ }
2779
+ ]
2780
+ }
2781
+ }
2782
+ ]
2783
+ });
2784
+ };
2785
+ updateLabel();
2786
+ this.countdownInterval = setInterval(updateLabel, 1e3);
2787
+ }
2788
+ stopCountdown() {
2789
+ if (this.countdownInterval) {
2790
+ clearInterval(this.countdownInterval);
2791
+ this.countdownInterval = null;
2792
+ }
2793
+ }
2794
+ addIndicator(id, plots, options = {}) {
2795
+ const isOverlay = options.overlay !== void 0 ? options.overlay : options.isOverlay ?? false;
2796
+ let paneIndex = 0;
2797
+ if (!isOverlay) {
2798
+ let maxPaneIndex = 0;
2799
+ this.indicators.forEach((ind) => {
2800
+ if (ind.paneIndex > maxPaneIndex) {
2801
+ maxPaneIndex = ind.paneIndex;
2802
+ }
2803
+ });
2804
+ paneIndex = maxPaneIndex + 1;
2805
+ }
2806
+ const indicator = new Indicator(id, plots, paneIndex, {
2807
+ height: options.height,
2808
+ collapsed: false,
2809
+ titleColor: options.titleColor,
2810
+ controls: options.controls
2811
+ });
2812
+ this.indicators.set(id, indicator);
2813
+ this.render();
2814
+ return indicator;
2815
+ }
2816
+ /** @deprecated Use addIndicator instead */
2817
+ setIndicator(id, plot, isOverlay = false) {
2818
+ this.addIndicator(id, { [id]: plot }, { overlay: isOverlay });
2819
+ }
2820
+ removeIndicator(id) {
2821
+ this.indicators.delete(id);
2822
+ this.render();
2823
+ }
2824
+ toggleIndicator(id, action = "collapse") {
2825
+ if (action === "fullscreen") {
2826
+ if (document.fullscreenElement) {
2827
+ document.exitFullscreen();
2828
+ } else {
2829
+ this.rootContainer.requestFullscreen();
2830
+ }
2831
+ return;
2832
+ }
2833
+ if (action === "maximize") {
2834
+ if (this.maximizedPaneId === id) {
2835
+ this.maximizedPaneId = null;
2836
+ } else {
2837
+ this.maximizedPaneId = id;
2838
+ }
2839
+ this.render();
2840
+ return;
2841
+ }
2842
+ if (id === "main") {
2843
+ this.isMainCollapsed = !this.isMainCollapsed;
2844
+ this.render();
2845
+ return;
2846
+ }
2847
+ const indicator = this.indicators.get(id);
2848
+ if (indicator) {
2849
+ indicator.toggleCollapse();
2850
+ this.render();
2851
+ }
2852
+ }
2853
+ resize() {
2854
+ this.chart.resize();
2855
+ }
2856
+ destroy() {
2857
+ this.stopCountdown();
2858
+ window.removeEventListener("resize", this.resize.bind(this));
2859
+ document.removeEventListener("fullscreenchange", this.onFullscreenChange);
2860
+ document.removeEventListener("keydown", this.onKeyDown);
2861
+ this.pluginManager.deactivatePlugin();
2862
+ this.pluginManager.destroy();
2863
+ this.chart.dispose();
2864
+ }
2865
+ rebuildTimeIndex() {
2866
+ this.timeToIndex.clear();
2867
+ this.marketData.forEach((k, index) => {
2868
+ this.timeToIndex.set(k.time, index);
2869
+ });
2870
+ const dataLength = this.marketData.length;
2871
+ const paddingPoints = Math.ceil(dataLength * this.padding);
2872
+ this.dataIndexOffset = paddingPoints;
2873
+ }
2874
+ render() {
2875
+ if (this.marketData.length === 0)
2876
+ return;
2877
+ let currentZoomState = null;
2878
+ try {
2879
+ const currentOption = this.chart.getOption();
2880
+ if (currentOption && currentOption.dataZoom && currentOption.dataZoom.length > 0) {
2881
+ const zoomComponent = currentOption.dataZoom.find((dz) => dz.type === "slider" || dz.type === "inside");
2882
+ if (zoomComponent) {
2883
+ currentZoomState = {
2884
+ start: zoomComponent.start,
2885
+ end: zoomComponent.end
2886
+ };
2887
+ }
2888
+ }
2889
+ } catch (e) {
2890
+ }
2891
+ const tooltipPos = this.options.databox?.position;
2892
+ const prevLeftDisplay = this.leftSidebar.style.display;
2893
+ const prevRightDisplay = this.rightSidebar.style.display;
2894
+ const newLeftDisplay = tooltipPos === "left" ? "block" : "none";
2895
+ const newRightDisplay = tooltipPos === "right" ? "block" : "none";
2896
+ if (prevLeftDisplay !== newLeftDisplay || prevRightDisplay !== newRightDisplay) {
2897
+ this.leftSidebar.style.display = newLeftDisplay;
2898
+ this.rightSidebar.style.display = newRightDisplay;
2899
+ this.chart.resize();
2900
+ }
2901
+ const paddingPoints = this.dataIndexOffset;
2902
+ const categoryData = [
2903
+ ...Array(paddingPoints).fill(""),
2904
+ // Left padding
2905
+ ...this.marketData.map((k) => new Date(k.time).toLocaleString()),
2906
+ ...Array(paddingPoints).fill("")
2907
+ // Right padding
2908
+ ];
2909
+ const layout = LayoutManager.calculate(
2910
+ this.chart.getHeight(),
2911
+ this.indicators,
2912
+ this.options,
2913
+ this.isMainCollapsed,
2914
+ this.maximizedPaneId,
2915
+ this.marketData
2916
+ );
2917
+ if (!currentZoomState && layout.dataZoom && this.marketData.length > 0) {
2918
+ const realDataLength = this.marketData.length;
2919
+ const totalLength = categoryData.length;
2920
+ const paddingRatio = paddingPoints / totalLength;
2921
+ const dataRatio = realDataLength / totalLength;
2922
+ layout.dataZoom.forEach((dz) => {
2923
+ if (dz.start !== void 0) {
2924
+ const userStartFraction = dz.start / 100;
2925
+ const actualStartFraction = paddingRatio + userStartFraction * dataRatio;
2926
+ dz.start = actualStartFraction * 100;
2927
+ }
2928
+ if (dz.end !== void 0) {
2929
+ const userEndFraction = dz.end / 100;
2930
+ const actualEndFraction = paddingRatio + userEndFraction * dataRatio;
2931
+ dz.end = actualEndFraction * 100;
2932
+ }
2933
+ });
2934
+ }
2935
+ if (currentZoomState && layout.dataZoom) {
2936
+ layout.dataZoom.forEach((dz) => {
2937
+ dz.start = currentZoomState.start;
2938
+ dz.end = currentZoomState.end;
2939
+ });
2940
+ }
2941
+ layout.xAxis.forEach((axis) => {
2942
+ axis.data = categoryData;
2943
+ axis.boundaryGap = false;
2944
+ });
2945
+ const candlestickSeries = SeriesBuilder.buildCandlestickSeries(this.marketData, this.options);
2946
+ const emptyCandle = { value: [NaN, NaN, NaN, NaN], itemStyle: { opacity: 0 } };
2947
+ candlestickSeries.data = [...Array(paddingPoints).fill(emptyCandle), ...candlestickSeries.data, ...Array(paddingPoints).fill(emptyCandle)];
2948
+ const paddedOHLCVForShapes = [...Array(paddingPoints).fill(null), ...this.marketData, ...Array(paddingPoints).fill(null)];
2949
+ const { series: indicatorSeries, barColors } = SeriesBuilder.buildIndicatorSeries(
2950
+ this.indicators,
2951
+ this.timeToIndex,
2952
+ layout.paneLayout,
2953
+ categoryData.length,
2954
+ paddingPoints,
2955
+ paddedOHLCVForShapes,
2956
+ // Pass padded OHLCV
2957
+ layout.overlayYAxisMap,
2958
+ // Pass overlay Y-axis mapping
2959
+ layout.separatePaneYAxisOffset
2960
+ // Pass Y-axis offset for separate panes
2961
+ );
2962
+ candlestickSeries.data = candlestickSeries.data.map((candle, i) => {
2963
+ if (barColors[i]) {
2964
+ return {
2965
+ value: candle.value || candle,
2966
+ itemStyle: {
2967
+ color: barColors[i],
2968
+ color0: barColors[i],
2969
+ borderColor: barColors[i],
2970
+ borderColor0: barColors[i]
2971
+ }
2972
+ };
2973
+ }
2974
+ return candle;
2975
+ });
2976
+ const graphic = GraphicBuilder.build(layout, this.options, this.toggleIndicator.bind(this), this.isMainCollapsed, this.maximizedPaneId);
2977
+ const drawingsByPane = /* @__PURE__ */ new Map();
2978
+ this.drawings.forEach((d) => {
2979
+ const paneIdx = d.paneIndex || 0;
2980
+ if (!drawingsByPane.has(paneIdx)) {
2981
+ drawingsByPane.set(paneIdx, []);
2982
+ }
2983
+ drawingsByPane.get(paneIdx).push(d);
2984
+ });
2985
+ const drawingSeriesList = [];
2986
+ drawingsByPane.forEach((drawings, paneIndex) => {
2987
+ drawingSeriesList.push({
2988
+ type: "custom",
2989
+ name: `drawings-pane-${paneIndex}`,
2990
+ xAxisIndex: paneIndex,
2991
+ yAxisIndex: paneIndex,
2992
+ clip: true,
2993
+ renderItem: (params, api) => {
2994
+ const drawing = drawings[params.dataIndex];
2995
+ if (!drawing)
2996
+ return;
2997
+ const start = drawing.points[0];
2998
+ const end = drawing.points[1];
2999
+ if (!start || !end)
3000
+ return;
3001
+ const p1 = api.coord([start.timeIndex, start.value]);
3002
+ const p2 = api.coord([end.timeIndex, end.value]);
3003
+ const isSelected = drawing.id === this.selectedDrawingId;
3004
+ if (drawing.type === "line") {
3005
+ return {
3006
+ type: "group",
3007
+ children: [
3008
+ {
3009
+ type: "line",
3010
+ name: "line",
3011
+ shape: {
3012
+ x1: p1[0],
3013
+ y1: p1[1],
3014
+ x2: p2[0],
3015
+ y2: p2[1]
3016
+ },
3017
+ style: {
3018
+ stroke: drawing.style?.color || "#3b82f6",
3019
+ lineWidth: drawing.style?.lineWidth || 2
3020
+ }
3021
+ },
3022
+ {
3023
+ type: "circle",
3024
+ name: "point-start",
3025
+ shape: { cx: p1[0], cy: p1[1], r: 4 },
3026
+ style: {
3027
+ fill: "#fff",
3028
+ stroke: drawing.style?.color || "#3b82f6",
3029
+ lineWidth: 1,
3030
+ opacity: isSelected ? 1 : 0
3031
+ // Show if selected
3032
+ }
3033
+ },
3034
+ {
3035
+ type: "circle",
3036
+ name: "point-end",
3037
+ shape: { cx: p2[0], cy: p2[1], r: 4 },
3038
+ style: {
3039
+ fill: "#fff",
3040
+ stroke: drawing.style?.color || "#3b82f6",
3041
+ lineWidth: 1,
3042
+ opacity: isSelected ? 1 : 0
3043
+ // Show if selected
3044
+ }
3045
+ }
3046
+ ]
3047
+ };
3048
+ } else if (drawing.type === "fibonacci") {
3049
+ const x1 = p1[0];
3050
+ const y1 = p1[1];
3051
+ const x2 = p2[0];
3052
+ const y2 = p2[1];
3053
+ const startX = Math.min(x1, x2);
3054
+ const endX = Math.max(x1, x2);
3055
+ const width = endX - startX;
3056
+ const diffY = y2 - y1;
3057
+ const levels = [0, 0.236, 0.382, 0.5, 0.618, 0.786, 1];
3058
+ const colors = ["#787b86", "#f44336", "#ff9800", "#4caf50", "#2196f3", "#00bcd4", "#787b86"];
3059
+ const children = [];
3060
+ children.push({
3061
+ type: "line",
3062
+ name: "line",
3063
+ // Use 'line' name to enable dragging logic in DrawingEditor
3064
+ shape: { x1, y1, x2, y2 },
3065
+ style: {
3066
+ stroke: "#999",
3067
+ lineWidth: 1,
3068
+ lineDash: [4, 4]
3069
+ }
3070
+ });
3071
+ children.push({
3072
+ type: "circle",
3073
+ name: "point-start",
3074
+ shape: { cx: x1, cy: y1, r: 4 },
3075
+ style: {
3076
+ fill: "#fff",
3077
+ stroke: drawing.style?.color || "#3b82f6",
3078
+ lineWidth: 1,
3079
+ opacity: isSelected ? 1 : 0
3080
+ },
3081
+ z: 100
3082
+ // Ensure on top
3083
+ });
3084
+ children.push({
3085
+ type: "circle",
3086
+ name: "point-end",
3087
+ shape: { cx: x2, cy: y2, r: 4 },
3088
+ style: {
3089
+ fill: "#fff",
3090
+ stroke: drawing.style?.color || "#3b82f6",
3091
+ lineWidth: 1,
3092
+ opacity: isSelected ? 1 : 0
3093
+ },
3094
+ z: 100
3095
+ });
3096
+ levels.forEach((level, index) => {
3097
+ const levelY = y2 - diffY * level;
3098
+ const color = colors[index % colors.length];
3099
+ children.push({
3100
+ type: "line",
3101
+ name: "fib-line",
3102
+ // distinct name, maybe we don't want to drag by clicking these lines? or yes? 'line' triggers drag. 'fib-line' won't unless we update logic.
3103
+ // The user asked for "fib levels between start and end".
3104
+ shape: { x1: startX, y1: levelY, x2: endX, y2: levelY },
3105
+ style: { stroke: color, lineWidth: 1 },
3106
+ silent: true
3107
+ // Make internal lines silent so clicks pass to background/diagonal?
3108
+ });
3109
+ const startVal = drawing.points[0].value;
3110
+ const endVal = drawing.points[1].value;
3111
+ const valDiff = endVal - startVal;
3112
+ const price = endVal - valDiff * level;
3113
+ children.push({
3114
+ type: "text",
3115
+ style: {
3116
+ text: `${level} (${price.toFixed(2)})`,
3117
+ x: startX + 5,
3118
+ y: levelY - 10,
3119
+ fill: color,
3120
+ fontSize: 10
3121
+ },
3122
+ silent: true
3123
+ });
3124
+ if (index < levels.length - 1) {
3125
+ const nextLevel = levels[index + 1];
3126
+ const nextY = y2 - diffY * nextLevel;
3127
+ const rectH = Math.abs(nextY - levelY);
3128
+ const rectY = Math.min(levelY, nextY);
3129
+ children.push({
3130
+ type: "rect",
3131
+ shape: { x: startX, y: rectY, width, height: rectH },
3132
+ style: {
3133
+ fill: colors[(index + 1) % colors.length],
3134
+ opacity: 0.1
3135
+ },
3136
+ silent: true
3137
+ // Let clicks pass through?
3138
+ });
3139
+ }
3140
+ });
3141
+ const backgrounds = [];
3142
+ const linesAndText = [];
3143
+ levels.forEach((level, index) => {
3144
+ const levelY = y2 - diffY * level;
3145
+ const color = colors[index % colors.length];
3146
+ linesAndText.push({
3147
+ type: "line",
3148
+ shape: { x1: startX, y1: levelY, x2: endX, y2: levelY },
3149
+ style: { stroke: color, lineWidth: 1 },
3150
+ silent: true
3151
+ });
3152
+ const startVal = drawing.points[0].value;
3153
+ const endVal = drawing.points[1].value;
3154
+ const valDiff = endVal - startVal;
3155
+ const price = endVal - valDiff * level;
3156
+ linesAndText.push({
3157
+ type: "text",
3158
+ style: {
3159
+ text: `${level} (${price.toFixed(2)})`,
3160
+ x: startX + 5,
3161
+ y: levelY - 10,
3162
+ fill: color,
3163
+ fontSize: 10
3164
+ },
3165
+ silent: true
3166
+ });
3167
+ if (index < levels.length - 1) {
3168
+ const nextLevel = levels[index + 1];
3169
+ const nextY = y2 - diffY * nextLevel;
3170
+ const rectH = Math.abs(nextY - levelY);
3171
+ const rectY = Math.min(levelY, nextY);
3172
+ backgrounds.push({
3173
+ type: "rect",
3174
+ name: "line",
3175
+ // Enable dragging by clicking background!
3176
+ shape: { x: startX, y: rectY, width, height: rectH },
3177
+ style: {
3178
+ fill: colors[(index + 1) % colors.length],
3179
+ opacity: 0.1
3180
+ }
3181
+ });
3182
+ }
3183
+ });
3184
+ return {
3185
+ type: "group",
3186
+ children: [
3187
+ ...backgrounds,
3188
+ ...linesAndText,
3189
+ {
3190
+ type: "line",
3191
+ name: "line",
3192
+ shape: { x1, y1, x2, y2 },
3193
+ style: { stroke: "#999", lineWidth: 1, lineDash: [4, 4] }
3194
+ },
3195
+ {
3196
+ type: "circle",
3197
+ name: "point-start",
3198
+ shape: { cx: x1, cy: y1, r: 4 },
3199
+ style: {
3200
+ fill: "#fff",
3201
+ stroke: drawing.style?.color || "#3b82f6",
3202
+ lineWidth: 1,
3203
+ opacity: isSelected ? 1 : 0
3204
+ },
3205
+ z: 100
3206
+ },
3207
+ {
3208
+ type: "circle",
3209
+ name: "point-end",
3210
+ shape: { cx: x2, cy: y2, r: 4 },
3211
+ style: {
3212
+ fill: "#fff",
3213
+ stroke: drawing.style?.color || "#3b82f6",
3214
+ lineWidth: 1,
3215
+ opacity: isSelected ? 1 : 0
3216
+ },
3217
+ z: 100
3218
+ }
3219
+ ]
3220
+ };
3221
+ }
3222
+ },
3223
+ data: drawings.map((d) => [d.points[0].timeIndex, d.points[0].value, d.points[1].timeIndex, d.points[1].value]),
3224
+ z: 100,
3225
+ silent: false
3226
+ });
3227
+ });
3228
+ const tooltipFormatter = (params) => {
3229
+ const html = TooltipFormatter.format(params, this.options);
3230
+ const mode = this.options.databox?.position;
3231
+ if (mode === "left") {
3232
+ this.leftSidebar.innerHTML = html;
3233
+ return "";
3234
+ }
3235
+ if (mode === "right") {
3236
+ this.rightSidebar.innerHTML = html;
3237
+ return "";
3238
+ }
3239
+ if (!this.options.databox) {
3240
+ return "";
3241
+ }
3242
+ return `<div style="min-width: 200px;">${html}</div>`;
3243
+ };
3244
+ const option = {
3245
+ backgroundColor: this.options.backgroundColor,
3246
+ animation: false,
3247
+ legend: {
3248
+ show: false
3249
+ // Hide default legend as we use tooltip
3250
+ },
3251
+ tooltip: {
3252
+ show: true,
3253
+ showContent: !!this.options.databox,
3254
+ // Show content only if databox is present
3255
+ trigger: "axis",
3256
+ triggerOn: this.options.databox?.triggerOn ?? "mousemove",
3257
+ // Control when to show tooltip/crosshair
3258
+ axisPointer: { type: "cross", label: { backgroundColor: "#475569" } },
3259
+ backgroundColor: "rgba(30, 41, 59, 0.9)",
3260
+ borderWidth: 1,
3261
+ borderColor: "#334155",
3262
+ padding: 10,
3263
+ textStyle: {
3264
+ color: "#fff",
3265
+ fontFamily: this.options.fontFamily || "sans-serif"
3266
+ },
3267
+ formatter: tooltipFormatter,
3268
+ extraCssText: tooltipPos !== "floating" && tooltipPos !== void 0 ? "display: none !important;" : void 0,
3269
+ position: (pos, params, el, elRect, size) => {
3270
+ const mode = this.options.databox?.position;
3271
+ if (mode === "floating") {
3272
+ const obj = { top: 10 };
3273
+ obj[["left", "right"][+(pos[0] < size.viewSize[0] / 2)]] = 30;
3274
+ return obj;
3275
+ }
3276
+ return null;
3277
+ }
3278
+ },
3279
+ axisPointer: {
3280
+ link: { xAxisIndex: "all" },
3281
+ label: { backgroundColor: "#475569" }
3282
+ },
3283
+ graphic,
3284
+ grid: layout.grid,
3285
+ xAxis: layout.xAxis,
3286
+ yAxis: layout.yAxis,
3287
+ dataZoom: layout.dataZoom,
3288
+ series: [candlestickSeries, ...indicatorSeries, ...drawingSeriesList]
3289
+ };
3290
+ this.chart.setOption(option, true);
3291
+ }
3292
+ }
3293
+
3294
+ var __defProp$3 = Object.defineProperty;
3295
+ var __defNormalProp$3 = (obj, key, value) => key in obj ? __defProp$3(obj, key, { enumerable: true, configurable: true, writable: true, value }) : obj[key] = value;
3296
+ var __publicField$3 = (obj, key, value) => {
3297
+ __defNormalProp$3(obj, typeof key !== "symbol" ? key + "" : key, value);
3298
+ return value;
3299
+ };
3300
+ class AbstractPlugin {
3301
+ constructor(config) {
3302
+ __publicField$3(this, "id");
3303
+ __publicField$3(this, "name");
3304
+ __publicField$3(this, "icon");
3305
+ __publicField$3(this, "context");
3306
+ __publicField$3(this, "eventListeners", []);
3307
+ this.id = config.id;
3308
+ this.name = config.name;
3309
+ this.icon = config.icon;
3310
+ }
3311
+ init(context) {
3312
+ this.context = context;
3313
+ this.onInit();
3314
+ }
3315
+ /**
3316
+ * Lifecycle hook called after context is initialized.
3317
+ * Override this instead of init().
3318
+ */
3319
+ onInit() {
3320
+ }
3321
+ activate() {
3322
+ this.onActivate();
3323
+ this.context.events.emit("plugin:activated", this.id);
3324
+ }
3325
+ /**
3326
+ * Lifecycle hook called when the plugin is activated.
3327
+ */
3328
+ onActivate() {
3329
+ }
3330
+ deactivate() {
3331
+ this.onDeactivate();
3332
+ this.context.events.emit("plugin:deactivated", this.id);
3333
+ }
3334
+ /**
3335
+ * Lifecycle hook called when the plugin is deactivated.
3336
+ */
3337
+ onDeactivate() {
3338
+ }
3339
+ destroy() {
3340
+ this.removeAllListeners();
3341
+ this.onDestroy();
3342
+ }
3343
+ /**
3344
+ * Lifecycle hook called when the plugin is destroyed.
3345
+ */
3346
+ onDestroy() {
3347
+ }
3348
+ // --- Helper Methods ---
3349
+ /**
3350
+ * Register an event listener that will be automatically cleaned up on destroy.
3351
+ */
3352
+ on(event, handler) {
3353
+ this.context.events.on(event, handler);
3354
+ this.eventListeners.push({ event, handler });
3355
+ }
3356
+ /**
3357
+ * Remove a specific event listener.
3358
+ */
3359
+ off(event, handler) {
3360
+ this.context.events.off(event, handler);
3361
+ this.eventListeners = this.eventListeners.filter(
3362
+ (l) => l.event !== event || l.handler !== handler
3363
+ );
3364
+ }
3365
+ /**
3366
+ * Remove all listeners registered by this plugin.
3367
+ */
3368
+ removeAllListeners() {
3369
+ this.eventListeners.forEach(({ event, handler }) => {
3370
+ this.context.events.off(event, handler);
3371
+ });
3372
+ this.eventListeners = [];
3373
+ }
3374
+ /**
3375
+ * Access to the ECharts instance.
3376
+ */
3377
+ get chart() {
3378
+ return this.context.getChart();
3379
+ }
3380
+ /**
3381
+ * Access to market data.
3382
+ */
3383
+ get marketData() {
3384
+ return this.context.getMarketData();
3385
+ }
3386
+ }
3387
+
3388
+ var __defProp$2 = Object.defineProperty;
3389
+ var __defNormalProp$2 = (obj, key, value) => key in obj ? __defProp$2(obj, key, { enumerable: true, configurable: true, writable: true, value }) : obj[key] = value;
3390
+ var __publicField$2 = (obj, key, value) => {
3391
+ __defNormalProp$2(obj, typeof key !== "symbol" ? key + "" : key, value);
3392
+ return value;
3393
+ };
3394
+ class MeasureTool extends AbstractPlugin {
3395
+ // End Arrow
3396
+ constructor(options) {
3397
+ super({
3398
+ id: "measure",
3399
+ name: options?.name || "Measure",
3400
+ icon: options?.icon || `<svg xmlns="http://www.w3.org/2000/svg" height="24px" viewBox="0 -960 960 960" width="24px" fill="#e3e3e3"><path d="M160-240q-33 0-56.5-23.5T80-320v-320q0-33 23.5-56.5T160-720h640q33 0 56.5 23.5T880-640v320q0 33-23.5 56.5T800-240H160Zm0-80h640v-320H680v160h-80v-160h-80v160h-80v-160h-80v160h-80v-160H160v320Zm120-160h80-80Zm160 0h80-80Zm160 0h80-80Zm-120 0Z"/></svg>`
3401
+ });
3402
+ __publicField$2(this, "zr");
3403
+ __publicField$2(this, "state", "idle");
3404
+ __publicField$2(this, "startPoint", null);
3405
+ __publicField$2(this, "endPoint", null);
3406
+ // ZRender Elements
3407
+ __publicField$2(this, "group", null);
3408
+ __publicField$2(this, "rect", null);
3409
+ // Measurement Box
3410
+ __publicField$2(this, "labelRect", null);
3411
+ // Label Background
3412
+ __publicField$2(this, "labelText", null);
3413
+ // Label Text
3414
+ __publicField$2(this, "lineV", null);
3415
+ // Vertical Arrow Line
3416
+ __publicField$2(this, "lineH", null);
3417
+ // Horizontal Arrow Line
3418
+ __publicField$2(this, "arrowStart", null);
3419
+ // Start Arrow
3420
+ __publicField$2(this, "arrowEnd", null);
3421
+ // --- Interaction Handlers ---
3422
+ __publicField$2(this, "onMouseDown", () => {
3423
+ if (this.state === "finished") {
3424
+ this.removeGraphic();
3425
+ }
3426
+ });
3427
+ __publicField$2(this, "onChartInteraction", () => {
3428
+ if (this.group) {
3429
+ this.removeGraphic();
3430
+ }
3431
+ });
3432
+ __publicField$2(this, "onClick", (params) => {
3433
+ if (this.state === "idle") {
3434
+ this.state = "drawing";
3435
+ this.startPoint = [params.offsetX, params.offsetY];
3436
+ this.endPoint = [params.offsetX, params.offsetY];
3437
+ this.initGraphic();
3438
+ this.updateGraphic();
3439
+ } else if (this.state === "drawing") {
3440
+ this.state = "finished";
3441
+ this.endPoint = [params.offsetX, params.offsetY];
3442
+ this.updateGraphic();
3443
+ this.context.disableTools();
3444
+ this.enableClearListeners();
3445
+ }
3446
+ });
3447
+ __publicField$2(this, "clearHandlers", {});
3448
+ __publicField$2(this, "onMouseMove", (params) => {
3449
+ if (this.state !== "drawing")
3450
+ return;
3451
+ this.endPoint = [params.offsetX, params.offsetY];
3452
+ this.updateGraphic();
3453
+ });
3454
+ }
3455
+ onInit() {
3456
+ this.zr = this.chart.getZr();
3457
+ }
3458
+ onActivate() {
3459
+ this.state = "idle";
3460
+ this.chart.getZr().setCursorStyle("crosshair");
3461
+ this.zr.on("click", this.onClick);
3462
+ this.zr.on("mousemove", this.onMouseMove);
3463
+ }
3464
+ onDeactivate() {
3465
+ this.state = "idle";
3466
+ this.chart.getZr().setCursorStyle("default");
3467
+ this.zr.off("click", this.onClick);
3468
+ this.zr.off("mousemove", this.onMouseMove);
3469
+ this.disableClearListeners();
3470
+ if (this.state === "drawing") {
3471
+ this.removeGraphic();
3472
+ }
3473
+ }
3474
+ onDestroy() {
3475
+ this.removeGraphic();
3476
+ }
3477
+ enableClearListeners() {
3478
+ const clickHandler = () => {
3479
+ this.removeGraphic();
3480
+ };
3481
+ setTimeout(() => {
3482
+ this.zr.on("click", clickHandler);
3483
+ }, 10);
3484
+ this.zr.on("mousedown", this.onMouseDown);
3485
+ this.context.events.on("chart:dataZoom", this.onChartInteraction);
3486
+ this.clearHandlers = {
3487
+ click: clickHandler,
3488
+ mousedown: this.onMouseDown,
3489
+ dataZoom: this.onChartInteraction
3490
+ };
3491
+ }
3492
+ disableClearListeners() {
3493
+ if (this.clearHandlers.click)
3494
+ this.zr.off("click", this.clearHandlers.click);
3495
+ if (this.clearHandlers.mousedown)
3496
+ this.zr.off("mousedown", this.clearHandlers.mousedown);
3497
+ if (this.clearHandlers.dataZoom) {
3498
+ this.context.events.off("chart:dataZoom", this.clearHandlers.dataZoom);
3499
+ }
3500
+ this.clearHandlers = {};
3501
+ }
3502
+ // --- Graphics ---
3503
+ initGraphic() {
3504
+ if (this.group)
3505
+ return;
3506
+ this.group = new echarts__namespace.graphic.Group();
3507
+ this.rect = new echarts__namespace.graphic.Rect({
3508
+ shape: { x: 0, y: 0, width: 0, height: 0 },
3509
+ style: { fill: "rgba(0,0,0,0)", stroke: "transparent", lineWidth: 0 },
3510
+ z: 100
3511
+ });
3512
+ this.lineV = new echarts__namespace.graphic.Line({
3513
+ shape: { x1: 0, y1: 0, x2: 0, y2: 0 },
3514
+ style: { stroke: "#fff", lineWidth: 1, lineDash: [4, 4] },
3515
+ z: 101
3516
+ });
3517
+ this.lineH = new echarts__namespace.graphic.Line({
3518
+ shape: { x1: 0, y1: 0, x2: 0, y2: 0 },
3519
+ style: { stroke: "#fff", lineWidth: 1, lineDash: [4, 4] },
3520
+ z: 101
3521
+ });
3522
+ this.arrowStart = new echarts__namespace.graphic.Polygon({
3523
+ shape: {
3524
+ points: [
3525
+ [0, 0],
3526
+ [-5, 10],
3527
+ [5, 10]
3528
+ ]
3529
+ },
3530
+ style: { fill: "#fff" },
3531
+ z: 102
3532
+ });
3533
+ this.arrowEnd = new echarts__namespace.graphic.Polygon({
3534
+ shape: {
3535
+ points: [
3536
+ [0, 0],
3537
+ [-5, -10],
3538
+ [5, -10]
3539
+ ]
3540
+ },
3541
+ style: { fill: "#fff" },
3542
+ z: 102
3543
+ });
3544
+ this.labelRect = new echarts__namespace.graphic.Rect({
3545
+ shape: { x: 0, y: 0, width: 0, height: 0, r: 4 },
3546
+ style: {
3547
+ fill: "transparent",
3548
+ stroke: "transparent",
3549
+ lineWidth: 0,
3550
+ shadowBlur: 5,
3551
+ shadowColor: "rgba(0,0,0,0.3)"
3552
+ },
3553
+ z: 102
3554
+ });
3555
+ this.labelText = new echarts__namespace.graphic.Text({
3556
+ style: {
3557
+ x: 0,
3558
+ y: 0,
3559
+ text: "",
3560
+ fill: "#fff",
3561
+ font: "12px sans-serif",
3562
+ align: "center",
3563
+ verticalAlign: "middle"
3564
+ },
3565
+ z: 103
3566
+ });
3567
+ this.group.add(this.rect);
3568
+ this.group.add(this.lineV);
3569
+ this.group.add(this.lineH);
3570
+ this.group.add(this.arrowStart);
3571
+ this.group.add(this.arrowEnd);
3572
+ this.group.add(this.labelRect);
3573
+ this.group.add(this.labelText);
3574
+ this.zr.add(this.group);
3575
+ }
3576
+ removeGraphic() {
3577
+ if (this.group) {
3578
+ this.zr.remove(this.group);
3579
+ this.group = null;
3580
+ this.disableClearListeners();
3581
+ }
3582
+ }
3583
+ updateGraphic() {
3584
+ if (!this.startPoint || !this.endPoint || !this.group)
3585
+ return;
3586
+ const [x1, y1] = this.startPoint;
3587
+ const [x2, y2] = this.endPoint;
3588
+ const p1 = this.context.coordinateConversion.pixelToData({ x: x1, y: y1 });
3589
+ const p2 = this.context.coordinateConversion.pixelToData({ x: x2, y: y2 });
3590
+ if (!p1 || !p2)
3591
+ return;
3592
+ const idx1 = Math.round(p1.timeIndex);
3593
+ const idx2 = Math.round(p2.timeIndex);
3594
+ const val1 = p1.value;
3595
+ const val2 = p2.value;
3596
+ const bars = idx2 - idx1;
3597
+ const priceDiff = val2 - val1;
3598
+ const priceChangePercent = priceDiff / val1 * 100;
3599
+ const isUp = priceDiff >= 0;
3600
+ const color = isUp ? "rgba(33, 150, 243, 0.2)" : "rgba(236, 0, 0, 0.2)";
3601
+ const strokeColor = isUp ? "#2196F3" : "#ec0000";
3602
+ this.rect.setShape({
3603
+ x: Math.min(x1, x2),
3604
+ y: Math.min(y1, y2),
3605
+ width: Math.abs(x2 - x1),
3606
+ height: Math.abs(y2 - y1)
3607
+ });
3608
+ this.rect.setStyle({ fill: color });
3609
+ const midX = (x1 + x2) / 2;
3610
+ const midY = (y1 + y2) / 2;
3611
+ this.lineV.setShape({ x1: midX, y1, x2: midX, y2 });
3612
+ this.lineV.setStyle({ stroke: strokeColor });
3613
+ this.lineH.setShape({ x1, y1: midY, x2, y2: midY });
3614
+ this.lineH.setStyle({ stroke: strokeColor });
3615
+ const topY = Math.min(y1, y2);
3616
+ const bottomY = Math.max(y1, y2);
3617
+ this.arrowStart.setStyle({ fill: "none" });
3618
+ this.arrowEnd.setStyle({ fill: "none" });
3619
+ if (isUp) {
3620
+ this.arrowStart.setShape({
3621
+ points: [
3622
+ [midX, topY],
3623
+ [midX - 4, topY + 6],
3624
+ [midX + 4, topY + 6]
3625
+ ]
3626
+ });
3627
+ this.arrowStart.setStyle({ fill: strokeColor });
3628
+ } else {
3629
+ this.arrowEnd.setShape({
3630
+ points: [
3631
+ [midX, bottomY],
3632
+ [midX - 4, bottomY - 6],
3633
+ [midX + 4, bottomY - 6]
3634
+ ]
3635
+ });
3636
+ this.arrowEnd.setStyle({ fill: strokeColor });
3637
+ }
3638
+ const textContent = [`${priceDiff.toFixed(2)} (${priceChangePercent.toFixed(2)}%)`, `${bars} bars, ${(bars * 0).toFixed(0)}d`].join("\n");
3639
+ const labelW = 140;
3640
+ const labelH = 40;
3641
+ const rectBottomY = Math.max(y1, y2);
3642
+ const rectTopY = Math.min(y1, y2);
3643
+ const rectCenterX = (x1 + x2) / 2;
3644
+ let labelX = rectCenterX - labelW / 2;
3645
+ let labelY = rectBottomY + 10;
3646
+ const canvasHeight = this.chart.getHeight();
3647
+ if (labelY + labelH > canvasHeight) {
3648
+ labelY = rectTopY - labelH - 10;
3649
+ }
3650
+ this.labelRect.setShape({
3651
+ x: labelX,
3652
+ y: labelY,
3653
+ width: labelW,
3654
+ height: labelH
3655
+ });
3656
+ this.labelRect.setStyle({
3657
+ fill: "#1e293b",
3658
+ stroke: strokeColor,
3659
+ lineWidth: 1
3660
+ });
3661
+ this.labelText.setStyle({
3662
+ x: labelX + labelW / 2,
3663
+ y: labelY + labelH / 2,
3664
+ text: textContent,
3665
+ fill: "#fff"
3666
+ });
3667
+ }
3668
+ }
3669
+
3670
+ var __defProp$1 = Object.defineProperty;
3671
+ var __defNormalProp$1 = (obj, key, value) => key in obj ? __defProp$1(obj, key, { enumerable: true, configurable: true, writable: true, value }) : obj[key] = value;
3672
+ var __publicField$1 = (obj, key, value) => {
3673
+ __defNormalProp$1(obj, typeof key !== "symbol" ? key + "" : key, value);
3674
+ return value;
3675
+ };
3676
+ class LineTool extends AbstractPlugin {
3677
+ constructor(options) {
3678
+ super({
3679
+ id: "trend-line",
3680
+ name: options?.name || "Trend Line",
3681
+ icon: options?.icon || `<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><line x1="2" y1="22" x2="22" y2="2" /></svg>`
3682
+ });
3683
+ __publicField$1(this, "zr");
3684
+ __publicField$1(this, "state", "idle");
3685
+ __publicField$1(this, "startPoint", null);
3686
+ __publicField$1(this, "endPoint", null);
3687
+ // ZRender Elements
3688
+ __publicField$1(this, "group", null);
3689
+ __publicField$1(this, "line", null);
3690
+ __publicField$1(this, "startCircle", null);
3691
+ __publicField$1(this, "endCircle", null);
3692
+ // --- Interaction Handlers ---
3693
+ __publicField$1(this, "onMouseDown", () => {
3694
+ });
3695
+ __publicField$1(this, "onChartInteraction", () => {
3696
+ });
3697
+ __publicField$1(this, "onClick", (params) => {
3698
+ if (this.state === "idle") {
3699
+ this.state = "drawing";
3700
+ this.startPoint = [params.offsetX, params.offsetY];
3701
+ this.endPoint = [params.offsetX, params.offsetY];
3702
+ this.initGraphic();
3703
+ this.updateGraphic();
3704
+ } else if (this.state === "drawing") {
3705
+ this.state = "finished";
3706
+ this.endPoint = [params.offsetX, params.offsetY];
3707
+ this.updateGraphic();
3708
+ if (this.startPoint && this.endPoint) {
3709
+ const start = this.context.coordinateConversion.pixelToData({
3710
+ x: this.startPoint[0],
3711
+ y: this.startPoint[1]
3712
+ });
3713
+ const end = this.context.coordinateConversion.pixelToData({
3714
+ x: this.endPoint[0],
3715
+ y: this.endPoint[1]
3716
+ });
3717
+ if (start && end) {
3718
+ const paneIndex = start.paneIndex || 0;
3719
+ this.context.addDrawing({
3720
+ id: `line-${Date.now()}`,
3721
+ type: "line",
3722
+ points: [start, end],
3723
+ paneIndex,
3724
+ style: {
3725
+ color: "#3b82f6",
3726
+ lineWidth: 2
3727
+ }
3728
+ });
3729
+ }
3730
+ }
3731
+ this.removeGraphic();
3732
+ this.context.disableTools();
3733
+ }
3734
+ });
3735
+ __publicField$1(this, "clearHandlers", {});
3736
+ __publicField$1(this, "onMouseMove", (params) => {
3737
+ if (this.state !== "drawing")
3738
+ return;
3739
+ this.endPoint = [params.offsetX, params.offsetY];
3740
+ this.updateGraphic();
3741
+ });
3742
+ }
3743
+ onInit() {
3744
+ this.zr = this.chart.getZr();
3745
+ }
3746
+ onActivate() {
3747
+ this.state = "idle";
3748
+ this.chart.getZr().setCursorStyle("crosshair");
3749
+ this.zr.on("click", this.onClick);
3750
+ this.zr.on("mousemove", this.onMouseMove);
3751
+ }
3752
+ onDeactivate() {
3753
+ this.state = "idle";
3754
+ this.chart.getZr().setCursorStyle("default");
3755
+ this.zr.off("click", this.onClick);
3756
+ this.zr.off("mousemove", this.onMouseMove);
3757
+ this.disableClearListeners();
3758
+ if (this.state === "drawing") {
3759
+ this.removeGraphic();
3760
+ }
3761
+ }
3762
+ onDestroy() {
3763
+ this.removeGraphic();
3764
+ }
3765
+ saveDataCoordinates() {
3766
+ }
3767
+ updateGraphicFromData() {
3768
+ }
3769
+ enableClearListeners() {
3770
+ }
3771
+ disableClearListeners() {
3772
+ }
3773
+ // --- Graphics ---
3774
+ initGraphic() {
3775
+ if (this.group)
3776
+ return;
3777
+ this.group = new echarts__namespace.graphic.Group();
3778
+ this.line = new echarts__namespace.graphic.Line({
3779
+ shape: { x1: 0, y1: 0, x2: 0, y2: 0 },
3780
+ style: { stroke: "#3b82f6", lineWidth: 2 },
3781
+ z: 100
3782
+ });
3783
+ this.startCircle = new echarts__namespace.graphic.Circle({
3784
+ shape: { cx: 0, cy: 0, r: 4 },
3785
+ style: { fill: "#fff", stroke: "#3b82f6", lineWidth: 1 },
3786
+ z: 101
3787
+ });
3788
+ this.endCircle = new echarts__namespace.graphic.Circle({
3789
+ shape: { cx: 0, cy: 0, r: 4 },
3790
+ style: { fill: "#fff", stroke: "#3b82f6", lineWidth: 1 },
3791
+ z: 101
3792
+ });
3793
+ this.group.add(this.line);
3794
+ this.group.add(this.startCircle);
3795
+ this.group.add(this.endCircle);
3796
+ this.zr.add(this.group);
3797
+ }
3798
+ removeGraphic() {
3799
+ if (this.group) {
3800
+ this.zr.remove(this.group);
3801
+ this.group = null;
3802
+ this.disableClearListeners();
3803
+ }
3804
+ }
3805
+ updateGraphic() {
3806
+ if (!this.startPoint || !this.endPoint || !this.group)
3807
+ return;
3808
+ const [x1, y1] = this.startPoint;
3809
+ const [x2, y2] = this.endPoint;
3810
+ this.line.setShape({ x1, y1, x2, y2 });
3811
+ this.startCircle.setShape({ cx: x1, cy: y1 });
3812
+ this.endCircle.setShape({ cx: x2, cy: y2 });
3813
+ }
3814
+ }
3815
+
3816
+ var __defProp = Object.defineProperty;
3817
+ var __defNormalProp = (obj, key, value) => key in obj ? __defProp(obj, key, { enumerable: true, configurable: true, writable: true, value }) : obj[key] = value;
3818
+ var __publicField = (obj, key, value) => {
3819
+ __defNormalProp(obj, typeof key !== "symbol" ? key + "" : key, value);
3820
+ return value;
3821
+ };
3822
+ class FibonacciTool extends AbstractPlugin {
3823
+ constructor(options = {}) {
3824
+ super({
3825
+ id: "fibonacci-tool",
3826
+ name: options.name || "Fibonacci Retracement",
3827
+ icon: options.icon || `<svg xmlns="http://www.w3.org/2000/svg" height="24px" viewBox="0 -960 960 960" width="24px" fill="#e3e3e3"><path d="M120-80v-80h720v80H120Zm0-240v-80h720v80H120Zm0-240v-80h720v80H120Zm0-240v-80h720v80H120Z"/></svg>`
3828
+ });
3829
+ __publicField(this, "startPoint", null);
3830
+ __publicField(this, "endPoint", null);
3831
+ __publicField(this, "state", "idle");
3832
+ // Temporary ZRender elements
3833
+ __publicField(this, "graphicGroup", null);
3834
+ // Fib levels config
3835
+ __publicField(this, "levels", [0, 0.236, 0.382, 0.5, 0.618, 0.786, 1]);
3836
+ __publicField(this, "colors", [
3837
+ "#787b86",
3838
+ // 0
3839
+ "#f44336",
3840
+ // 0.236
3841
+ "#ff9800",
3842
+ // 0.382
3843
+ "#4caf50",
3844
+ // 0.5
3845
+ "#2196f3",
3846
+ // 0.618
3847
+ "#00bcd4",
3848
+ // 0.786
3849
+ "#787b86"
3850
+ // 1
3851
+ ]);
3852
+ __publicField(this, "onClick", (params) => {
3853
+ if (this.state === "idle") {
3854
+ this.state = "drawing";
3855
+ this.startPoint = [params.offsetX, params.offsetY];
3856
+ this.endPoint = [params.offsetX, params.offsetY];
3857
+ this.initGraphic();
3858
+ this.updateGraphic();
3859
+ } else if (this.state === "drawing") {
3860
+ this.state = "finished";
3861
+ this.endPoint = [params.offsetX, params.offsetY];
3862
+ this.updateGraphic();
3863
+ this.saveDrawing();
3864
+ this.removeGraphic();
3865
+ this.context.disableTools();
3866
+ }
3867
+ });
3868
+ __publicField(this, "onMouseMove", (params) => {
3869
+ if (this.state === "drawing") {
3870
+ this.endPoint = [params.offsetX, params.offsetY];
3871
+ this.updateGraphic();
3872
+ }
3873
+ });
3874
+ }
3875
+ onActivate() {
3876
+ this.state = "idle";
3877
+ this.startPoint = null;
3878
+ this.endPoint = null;
3879
+ this.context.getChart().getZr().setCursorStyle("crosshair");
3880
+ this.bindEvents();
3881
+ }
3882
+ onDeactivate() {
3883
+ this.state = "idle";
3884
+ this.startPoint = null;
3885
+ this.endPoint = null;
3886
+ this.removeGraphic();
3887
+ this.unbindEvents();
3888
+ this.context.getChart().getZr().setCursorStyle("default");
3889
+ }
3890
+ bindEvents() {
3891
+ const zr = this.context.getChart().getZr();
3892
+ zr.on("click", this.onClick);
3893
+ zr.on("mousemove", this.onMouseMove);
3894
+ }
3895
+ unbindEvents() {
3896
+ const zr = this.context.getChart().getZr();
3897
+ zr.off("click", this.onClick);
3898
+ zr.off("mousemove", this.onMouseMove);
3899
+ }
3900
+ initGraphic() {
3901
+ this.graphicGroup = new echarts__namespace.graphic.Group();
3902
+ this.context.getChart().getZr().add(this.graphicGroup);
3903
+ }
3904
+ removeGraphic() {
3905
+ if (this.graphicGroup) {
3906
+ this.context.getChart().getZr().remove(this.graphicGroup);
3907
+ this.graphicGroup = null;
3908
+ }
3909
+ }
3910
+ updateGraphic() {
3911
+ if (!this.graphicGroup || !this.startPoint || !this.endPoint)
3912
+ return;
3913
+ this.graphicGroup.removeAll();
3914
+ const x1 = this.startPoint[0];
3915
+ const y1 = this.startPoint[1];
3916
+ const x2 = this.endPoint[0];
3917
+ const y2 = this.endPoint[1];
3918
+ const trendLine = new echarts__namespace.graphic.Line({
3919
+ shape: { x1, y1, x2, y2 },
3920
+ style: {
3921
+ stroke: "#999",
3922
+ lineWidth: 1,
3923
+ lineDash: [4, 4]
3924
+ },
3925
+ silent: true
3926
+ });
3927
+ this.graphicGroup.add(trendLine);
3928
+ const startX = Math.min(x1, x2);
3929
+ const endX = Math.max(x1, x2);
3930
+ const width = endX - startX;
3931
+ const diffY = y2 - y1;
3932
+ this.levels.forEach((level, index) => {
3933
+ const levelY = y2 - diffY * level;
3934
+ const color = this.colors[index % this.colors.length];
3935
+ const line = new echarts__namespace.graphic.Line({
3936
+ shape: { x1: startX, y1: levelY, x2: endX, y2: levelY },
3937
+ style: {
3938
+ stroke: color,
3939
+ lineWidth: 1
3940
+ },
3941
+ silent: true
3942
+ });
3943
+ this.graphicGroup.add(line);
3944
+ if (index < this.levels.length - 1) {
3945
+ const nextLevel = this.levels[index + 1];
3946
+ const nextY = y2 - diffY * nextLevel;
3947
+ const rectH = Math.abs(nextY - levelY);
3948
+ const rectY = Math.min(levelY, nextY);
3949
+ const rect = new echarts__namespace.graphic.Rect({
3950
+ shape: { x: startX, y: rectY, width, height: rectH },
3951
+ style: {
3952
+ fill: this.colors[(index + 1) % this.colors.length],
3953
+ // Use next level's color
3954
+ opacity: 0.1
3955
+ },
3956
+ silent: true
3957
+ });
3958
+ this.graphicGroup.add(rect);
3959
+ }
3960
+ });
3961
+ }
3962
+ saveDrawing() {
3963
+ if (!this.startPoint || !this.endPoint)
3964
+ return;
3965
+ const start = this.context.coordinateConversion.pixelToData({
3966
+ x: this.startPoint[0],
3967
+ y: this.startPoint[1]
3968
+ });
3969
+ const end = this.context.coordinateConversion.pixelToData({
3970
+ x: this.endPoint[0],
3971
+ y: this.endPoint[1]
3972
+ });
3973
+ if (start && end) {
3974
+ const paneIndex = start.paneIndex || 0;
3975
+ this.context.addDrawing({
3976
+ id: `fib-${Date.now()}`,
3977
+ type: "fibonacci",
3978
+ points: [start, end],
3979
+ paneIndex,
3980
+ style: {
3981
+ color: "#3b82f6",
3982
+ // Default color, though individual lines use specific colors
3983
+ lineWidth: 1
3984
+ }
3985
+ });
3986
+ }
3987
+ }
3988
+ }
3989
+
3990
+ exports.AbstractPlugin = AbstractPlugin;
3991
+ exports.FibonacciTool = FibonacciTool;
3992
+ exports.LineTool = LineTool;
3993
+ exports.MeasureTool = MeasureTool;
3994
+ exports.QFChart = QFChart;
3995
+
3996
+ }));
3997
+ //# sourceMappingURL=qfchart.dev.browser.js.map