@qfo/qfchart 0.6.4 → 0.6.6

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.
@@ -1,4 +1,5 @@
1
- import { QFChartOptions, Indicator as IndicatorType } from '../types';
1
+ import { QFChartOptions, Indicator as IndicatorType, OHLCV } from '../types';
2
+ import { AxisUtils } from '../utils/AxisUtils';
2
3
 
3
4
  export interface PaneConfiguration {
4
5
  index: number;
@@ -42,23 +43,6 @@ export class LayoutManager {
42
43
  // Get Y-axis padding percentage (default 5%)
43
44
  const yAxisPaddingPercent = options.yAxisPadding !== undefined ? options.yAxisPadding : 5;
44
45
 
45
- // Create min/max functions that apply padding
46
- const createMinFunction = (paddingPercent: number) => {
47
- return (value: any) => {
48
- const range = value.max - value.min;
49
- const padding = range * (paddingPercent / 100);
50
- return value.min - padding;
51
- };
52
- };
53
-
54
- const createMaxFunction = (paddingPercent: number) => {
55
- return (value: any) => {
56
- const range = value.max - value.min;
57
- const padding = range * (paddingPercent / 100);
58
- return value.max + padding;
59
- };
60
- };
61
-
62
46
  // Identify unique separate panes (indices > 0) and sort them
63
47
  const separatePaneIndices = Array.from(indicators.values())
64
48
  .map((ind) => ind.paneIndex)
@@ -163,12 +147,18 @@ export class LayoutManager {
163
147
 
164
148
  if (i === 0 && maximizeTargetIndex === 0) {
165
149
  // Main pane is maximized, use custom values if provided
166
- yMin = options.yAxisMin !== undefined && options.yAxisMin !== 'auto' ? options.yAxisMin : createMinFunction(yAxisPaddingPercent);
167
- yMax = options.yAxisMax !== undefined && options.yAxisMax !== 'auto' ? options.yAxisMax : createMaxFunction(yAxisPaddingPercent);
150
+ yMin =
151
+ options.yAxisMin !== undefined && options.yAxisMin !== 'auto'
152
+ ? options.yAxisMin
153
+ : AxisUtils.createMinFunction(yAxisPaddingPercent);
154
+ yMax =
155
+ options.yAxisMax !== undefined && options.yAxisMax !== 'auto'
156
+ ? options.yAxisMax
157
+ : AxisUtils.createMaxFunction(yAxisPaddingPercent);
168
158
  } else {
169
159
  // Separate panes always use dynamic scaling
170
- yMin = createMinFunction(yAxisPaddingPercent);
171
- yMax = createMaxFunction(yAxisPaddingPercent);
160
+ yMin = AxisUtils.createMinFunction(yAxisPaddingPercent);
161
+ yMax = AxisUtils.createMaxFunction(yAxisPaddingPercent);
172
162
  }
173
163
 
174
164
  yAxis.push({
@@ -186,11 +176,10 @@ export class LayoutManager {
186
176
  if (options.yAxisLabelFormatter) {
187
177
  return options.yAxisLabelFormatter(value);
188
178
  }
189
- const decimals = options.yAxisDecimalPlaces !== undefined ? options.yAxisDecimalPlaces : 2;
190
- if (typeof value === 'number') {
191
- return value.toFixed(decimals);
192
- }
193
- return String(value);
179
+ const decimals = options.yAxisDecimalPlaces !== undefined
180
+ ? options.yAxisDecimalPlaces
181
+ : AxisUtils.autoDetectDecimals(marketData as OHLCV[]);
182
+ return AxisUtils.formatValue(value, decimals);
194
183
  },
195
184
  },
196
185
  splitLine: {
@@ -377,11 +366,10 @@ export class LayoutManager {
377
366
  if (options.yAxisLabelFormatter) {
378
367
  return options.yAxisLabelFormatter(value);
379
368
  }
380
- const decimals = options.yAxisDecimalPlaces !== undefined ? options.yAxisDecimalPlaces : 2;
381
- if (typeof value === 'number') {
382
- return value.toFixed(decimals);
383
- }
384
- return String(value);
369
+ const decimals = options.yAxisDecimalPlaces !== undefined
370
+ ? options.yAxisDecimalPlaces
371
+ : AxisUtils.autoDetectDecimals(marketData as OHLCV[]);
372
+ return AxisUtils.formatValue(value, decimals);
385
373
  },
386
374
  },
387
375
  axisTick: { show: !isMainCollapsed },
@@ -425,13 +413,13 @@ export class LayoutManager {
425
413
  if (options.yAxisMin !== undefined && options.yAxisMin !== 'auto') {
426
414
  mainYAxisMin = options.yAxisMin;
427
415
  } else {
428
- mainYAxisMin = createMinFunction(yAxisPaddingPercent);
416
+ mainYAxisMin = AxisUtils.createMinFunction(yAxisPaddingPercent);
429
417
  }
430
418
 
431
419
  if (options.yAxisMax !== undefined && options.yAxisMax !== 'auto') {
432
420
  mainYAxisMax = options.yAxisMax;
433
421
  } else {
434
- mainYAxisMax = createMaxFunction(yAxisPaddingPercent);
422
+ mainYAxisMax = AxisUtils.createMaxFunction(yAxisPaddingPercent);
435
423
  }
436
424
 
437
425
  // Main Y-Axis (for candlesticks)
@@ -454,11 +442,10 @@ export class LayoutManager {
454
442
  if (options.yAxisLabelFormatter) {
455
443
  return options.yAxisLabelFormatter(value);
456
444
  }
457
- const decimals = options.yAxisDecimalPlaces !== undefined ? options.yAxisDecimalPlaces : 2;
458
- if (typeof value === 'number') {
459
- return value.toFixed(decimals);
460
- }
461
- return String(value);
445
+ const decimals = options.yAxisDecimalPlaces !== undefined
446
+ ? options.yAxisDecimalPlaces
447
+ : AxisUtils.autoDetectDecimals(marketData as OHLCV[]);
448
+ return AxisUtils.formatValue(value, decimals);
462
449
  },
463
450
  },
464
451
  });
@@ -554,12 +541,30 @@ export class LayoutManager {
554
541
  // Create Y-axes for incompatible plots
555
542
  // nextYAxisIndex already incremented in the loop above, so we know how many axes we need
556
543
  const numOverlayAxes = overlayYAxisMap.size > 0 ? nextYAxisIndex - 1 : 0;
544
+
545
+ // Track which overlay axes are for visual-only plots (background, barcolor, etc.)
546
+ const visualOnlyAxes = new Set<number>();
547
+ overlayYAxisMap.forEach((yAxisIdx, plotKey) => {
548
+ // Check if this plot is visual-only by looking at the original indicator
549
+ indicators.forEach((indicator) => {
550
+ Object.entries(indicator.plots).forEach(([plotName, plot]) => {
551
+ const key = `${indicator.id}::${plotName}`;
552
+ if (key === plotKey && ['background', 'barcolor', 'char'].includes(plot.options.style)) {
553
+ visualOnlyAxes.add(yAxisIdx);
554
+ }
555
+ });
556
+ });
557
+ });
558
+
557
559
  for (let i = 0; i < numOverlayAxes; i++) {
560
+ const yAxisIndex = i + 1; // Y-axis indices start at 1 for overlays
561
+ const isVisualOnly = visualOnlyAxes.has(yAxisIndex);
562
+
558
563
  yAxis.push({
559
564
  position: 'left',
560
- scale: true,
561
- min: createMinFunction(yAxisPaddingPercent),
562
- max: createMaxFunction(yAxisPaddingPercent),
565
+ scale: !isVisualOnly, // Disable scaling for visual-only plots
566
+ min: isVisualOnly ? 0 : AxisUtils.createMinFunction(yAxisPaddingPercent), // Fixed range for visual plots
567
+ max: isVisualOnly ? 1 : AxisUtils.createMaxFunction(yAxisPaddingPercent), // Fixed range for visual plots
563
568
  gridIndex: 0,
564
569
  show: false, // Hide the axis visual elements
565
570
  splitLine: { show: false },
@@ -574,8 +579,8 @@ export class LayoutManager {
574
579
  yAxis.push({
575
580
  position: 'right',
576
581
  scale: true,
577
- min: createMinFunction(yAxisPaddingPercent),
578
- max: createMaxFunction(yAxisPaddingPercent),
582
+ min: AxisUtils.createMinFunction(yAxisPaddingPercent),
583
+ max: AxisUtils.createMaxFunction(yAxisPaddingPercent),
579
584
  gridIndex: i + 1,
580
585
  splitLine: {
581
586
  show: !pane.isCollapsed,
@@ -590,11 +595,10 @@ export class LayoutManager {
590
595
  if (options.yAxisLabelFormatter) {
591
596
  return options.yAxisLabelFormatter(value);
592
597
  }
593
- const decimals = options.yAxisDecimalPlaces !== undefined ? options.yAxisDecimalPlaces : 2;
594
- if (typeof value === 'number') {
595
- return value.toFixed(decimals);
596
- }
597
- return String(value);
598
+ const decimals = options.yAxisDecimalPlaces !== undefined
599
+ ? options.yAxisDecimalPlaces
600
+ : AxisUtils.autoDetectDecimals(marketData as OHLCV[]);
601
+ return AxisUtils.formatValue(value, decimals);
598
602
  },
599
603
  },
600
604
  axisLine: { show: !pane.isCollapsed, lineStyle: { color: '#334155' } },