@qfo/qfchart 0.8.1 → 0.8.4
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.
- package/dist/index.d.ts +216 -1
- package/dist/qfchart.min.browser.js +20 -19
- package/dist/qfchart.min.es.js +18 -17
- package/package.json +1 -1
- package/src/QFChart.ts +146 -11
- package/src/components/LayoutManager.ts +76 -28
- package/src/components/PluginManager.ts +229 -229
- package/src/components/SeriesBuilder.ts +21 -14
- package/src/components/renderers/LabelRenderer.ts +6 -3
- package/src/components/renderers/ScatterRenderer.ts +92 -54
- package/src/components/renderers/ShapeRenderer.ts +12 -0
- package/src/index.ts +8 -0
- package/src/plugins/CrossLineTool/CrossLineDrawingRenderer.ts +49 -0
- package/src/plugins/CrossLineTool/CrossLineTool.ts +52 -0
- package/src/plugins/CrossLineTool/index.ts +2 -0
- package/src/plugins/ExtendedLineTool/ExtendedLineDrawingRenderer.ts +73 -0
- package/src/plugins/ExtendedLineTool/ExtendedLineTool.ts +173 -0
- package/src/plugins/ExtendedLineTool/index.ts +2 -0
- package/src/plugins/HorizontalLineTool/HorizontalLineDrawingRenderer.ts +54 -0
- package/src/plugins/HorizontalLineTool/HorizontalLineTool.ts +52 -0
- package/src/plugins/HorizontalLineTool/index.ts +2 -0
- package/src/plugins/HorizontalRayTool/HorizontalRayDrawingRenderer.ts +34 -0
- package/src/plugins/HorizontalRayTool/HorizontalRayTool.ts +52 -0
- package/src/plugins/HorizontalRayTool/index.ts +2 -0
- package/src/plugins/InfoLineTool/InfoLineDrawingRenderer.ts +72 -0
- package/src/plugins/InfoLineTool/InfoLineTool.ts +130 -0
- package/src/plugins/InfoLineTool/index.ts +2 -0
- package/src/plugins/LineTool/LineDrawingRenderer.ts +2 -2
- package/src/plugins/LineTool/LineTool.ts +5 -5
- package/src/plugins/RayTool/RayDrawingRenderer.ts +69 -0
- package/src/plugins/RayTool/RayTool.ts +162 -0
- package/src/plugins/RayTool/index.ts +2 -0
- package/src/plugins/TrendAngleTool/TrendAngleDrawingRenderer.ts +87 -0
- package/src/plugins/TrendAngleTool/TrendAngleTool.ts +176 -0
- package/src/plugins/TrendAngleTool/index.ts +2 -0
- package/src/plugins/VerticalLineTool/VerticalLineDrawingRenderer.ts +35 -0
- package/src/plugins/VerticalLineTool/VerticalLineTool.ts +52 -0
- package/src/plugins/VerticalLineTool/index.ts +2 -0
- package/src/types.ts +2 -0
package/package.json
CHANGED
package/src/QFChart.ts
CHANGED
|
@@ -984,6 +984,9 @@ export class QFChart implements ChartContext {
|
|
|
984
984
|
return candle;
|
|
985
985
|
});
|
|
986
986
|
|
|
987
|
+
// Build drawing range hints for Y-axis scaling
|
|
988
|
+
const updateDrawingRangeHints = this._buildDrawingRangeHints(layout, paddingPoints);
|
|
989
|
+
|
|
987
990
|
// Update only the data arrays in the option, not the full config
|
|
988
991
|
const updateOption: any = {
|
|
989
992
|
xAxis: currentOption.xAxis.map((axis: any, index: number) => ({
|
|
@@ -995,6 +998,7 @@ export class QFChart implements ChartContext {
|
|
|
995
998
|
markLine: candlestickSeries.markLine, // Ensure markLine is updated
|
|
996
999
|
},
|
|
997
1000
|
...indicatorSeries,
|
|
1001
|
+
...updateDrawingRangeHints,
|
|
998
1002
|
],
|
|
999
1003
|
};
|
|
1000
1004
|
|
|
@@ -1232,6 +1236,132 @@ export class QFChart implements ChartContext {
|
|
|
1232
1236
|
this._renderTableOverlays();
|
|
1233
1237
|
}
|
|
1234
1238
|
|
|
1239
|
+
/**
|
|
1240
|
+
* Build invisible "scatter" series that carry the min/max Y values of Pine
|
|
1241
|
+
* Script drawing objects (lines, boxes, labels, polylines). ECharts includes
|
|
1242
|
+
* these points in its automatic Y-axis range calculation so drawings below
|
|
1243
|
+
* or above the candlestick range are no longer clipped.
|
|
1244
|
+
*
|
|
1245
|
+
* Returns one hidden series per pane that has drawing objects with Y-values
|
|
1246
|
+
* outside the default data range.
|
|
1247
|
+
*/
|
|
1248
|
+
private _buildDrawingRangeHints(layout: any, paddingPoints: number): any[] {
|
|
1249
|
+
const hintSeries: any[] = [];
|
|
1250
|
+
|
|
1251
|
+
// Collect Y-value bounds per pane from all indicator drawing objects
|
|
1252
|
+
const boundsPerPane = new Map<number, { yMin: number; yMax: number }>();
|
|
1253
|
+
|
|
1254
|
+
for (const indicator of this.indicators) {
|
|
1255
|
+
if (!indicator.plots) continue;
|
|
1256
|
+
const paneIndex = indicator.paneIndex ?? 0;
|
|
1257
|
+
if (!boundsPerPane.has(paneIndex)) {
|
|
1258
|
+
boundsPerPane.set(paneIndex, { yMin: Infinity, yMax: -Infinity });
|
|
1259
|
+
}
|
|
1260
|
+
const bounds = boundsPerPane.get(paneIndex)!;
|
|
1261
|
+
|
|
1262
|
+
for (const [plotName, plot] of Object.entries(indicator.plots as Record<string, any>)) {
|
|
1263
|
+
if (!plot || !plot.options) continue;
|
|
1264
|
+
const style = plot.options?.style;
|
|
1265
|
+
|
|
1266
|
+
// Lines: y1, y2
|
|
1267
|
+
if (style === 'drawing_line' && plot.data) {
|
|
1268
|
+
for (const entry of plot.data) {
|
|
1269
|
+
const items = entry?.value ? (Array.isArray(entry.value) ? entry.value : [entry.value]) : [];
|
|
1270
|
+
for (const ln of items) {
|
|
1271
|
+
if (!ln || ln._deleted) continue;
|
|
1272
|
+
if (typeof ln.y1 === 'number' && isFinite(ln.y1)) {
|
|
1273
|
+
bounds.yMin = Math.min(bounds.yMin, ln.y1);
|
|
1274
|
+
bounds.yMax = Math.max(bounds.yMax, ln.y1);
|
|
1275
|
+
}
|
|
1276
|
+
if (typeof ln.y2 === 'number' && isFinite(ln.y2)) {
|
|
1277
|
+
bounds.yMin = Math.min(bounds.yMin, ln.y2);
|
|
1278
|
+
bounds.yMax = Math.max(bounds.yMax, ln.y2);
|
|
1279
|
+
}
|
|
1280
|
+
}
|
|
1281
|
+
}
|
|
1282
|
+
}
|
|
1283
|
+
|
|
1284
|
+
// Boxes: top, bottom
|
|
1285
|
+
if (style === 'drawing_box' && plot.data) {
|
|
1286
|
+
for (const entry of plot.data) {
|
|
1287
|
+
const items = entry?.value ? (Array.isArray(entry.value) ? entry.value : [entry.value]) : [];
|
|
1288
|
+
for (const bx of items) {
|
|
1289
|
+
if (!bx || bx._deleted) continue;
|
|
1290
|
+
if (typeof bx.top === 'number' && isFinite(bx.top)) {
|
|
1291
|
+
bounds.yMin = Math.min(bounds.yMin, bx.top);
|
|
1292
|
+
bounds.yMax = Math.max(bounds.yMax, bx.top);
|
|
1293
|
+
}
|
|
1294
|
+
if (typeof bx.bottom === 'number' && isFinite(bx.bottom)) {
|
|
1295
|
+
bounds.yMin = Math.min(bounds.yMin, bx.bottom);
|
|
1296
|
+
bounds.yMax = Math.max(bounds.yMax, bx.bottom);
|
|
1297
|
+
}
|
|
1298
|
+
}
|
|
1299
|
+
}
|
|
1300
|
+
}
|
|
1301
|
+
|
|
1302
|
+
// Labels: y
|
|
1303
|
+
if (style === 'label' && plot.data) {
|
|
1304
|
+
for (const entry of plot.data) {
|
|
1305
|
+
const items = entry?.value ? (Array.isArray(entry.value) ? entry.value : [entry.value]) : [];
|
|
1306
|
+
for (const lbl of items) {
|
|
1307
|
+
if (!lbl || lbl._deleted) continue;
|
|
1308
|
+
if (typeof lbl.y === 'number' && isFinite(lbl.y)) {
|
|
1309
|
+
bounds.yMin = Math.min(bounds.yMin, lbl.y);
|
|
1310
|
+
bounds.yMax = Math.max(bounds.yMax, lbl.y);
|
|
1311
|
+
}
|
|
1312
|
+
}
|
|
1313
|
+
}
|
|
1314
|
+
}
|
|
1315
|
+
|
|
1316
|
+
// Polylines: points[].price
|
|
1317
|
+
if (style === 'drawing_polyline' && plot.data) {
|
|
1318
|
+
for (const entry of plot.data) {
|
|
1319
|
+
const items = entry?.value ? (Array.isArray(entry.value) ? entry.value : [entry.value]) : [];
|
|
1320
|
+
for (const pl of items) {
|
|
1321
|
+
if (!pl || pl._deleted || !pl._points) continue;
|
|
1322
|
+
for (const pt of pl._points) {
|
|
1323
|
+
if (typeof pt?.price === 'number' && isFinite(pt.price)) {
|
|
1324
|
+
bounds.yMin = Math.min(bounds.yMin, pt.price);
|
|
1325
|
+
bounds.yMax = Math.max(bounds.yMax, pt.price);
|
|
1326
|
+
}
|
|
1327
|
+
}
|
|
1328
|
+
}
|
|
1329
|
+
}
|
|
1330
|
+
}
|
|
1331
|
+
}
|
|
1332
|
+
}
|
|
1333
|
+
|
|
1334
|
+
// Create a hidden scatter series per pane with min/max Y values
|
|
1335
|
+
const midIndex = paddingPoints + Math.floor((this.marketData?.length || 0) / 2);
|
|
1336
|
+
boundsPerPane.forEach((bounds, paneIndex) => {
|
|
1337
|
+
if (!isFinite(bounds.yMin) || !isFinite(bounds.yMax)) return;
|
|
1338
|
+
|
|
1339
|
+
// Determine Y-axis index for this pane
|
|
1340
|
+
const yAxisIndex = paneIndex === 0
|
|
1341
|
+
? 0
|
|
1342
|
+
: (layout.separatePaneYAxisOffset || 1) + (paneIndex - 1);
|
|
1343
|
+
|
|
1344
|
+
hintSeries.push({
|
|
1345
|
+
name: `_drawingRange_pane${paneIndex}`,
|
|
1346
|
+
type: 'scatter',
|
|
1347
|
+
xAxisIndex: paneIndex,
|
|
1348
|
+
yAxisIndex,
|
|
1349
|
+
symbol: 'none',
|
|
1350
|
+
symbolSize: 0,
|
|
1351
|
+
silent: true,
|
|
1352
|
+
animation: false,
|
|
1353
|
+
// Two invisible points at min and max Y — ECharts includes them in axis scaling
|
|
1354
|
+
data: [
|
|
1355
|
+
[midIndex, bounds.yMin],
|
|
1356
|
+
[midIndex, bounds.yMax],
|
|
1357
|
+
],
|
|
1358
|
+
tooltip: { show: false },
|
|
1359
|
+
});
|
|
1360
|
+
});
|
|
1361
|
+
|
|
1362
|
+
return hintSeries;
|
|
1363
|
+
}
|
|
1364
|
+
|
|
1235
1365
|
/**
|
|
1236
1366
|
* Build table canvas graphic elements from the current _lastTables.
|
|
1237
1367
|
* Must be called AFTER setOption so grid rects are available from ECharts.
|
|
@@ -1397,22 +1527,22 @@ export class QFChart implements ChartContext {
|
|
|
1397
1527
|
});
|
|
1398
1528
|
drawingsByPane.forEach((paneDrawings) => {
|
|
1399
1529
|
drawingSeriesUpdates.push({
|
|
1400
|
-
data: paneDrawings.map((d) =>
|
|
1401
|
-
|
|
1402
|
-
d.points
|
|
1403
|
-
|
|
1404
|
-
|
|
1405
|
-
|
|
1530
|
+
data: paneDrawings.map((d) => {
|
|
1531
|
+
const row: number[] = [];
|
|
1532
|
+
d.points.forEach((p) => {
|
|
1533
|
+
row.push(p.timeIndex + this.dataIndexOffset, p.value);
|
|
1534
|
+
});
|
|
1535
|
+
return row;
|
|
1536
|
+
}),
|
|
1406
1537
|
});
|
|
1407
1538
|
});
|
|
1408
1539
|
|
|
1409
1540
|
// 6. Merge update — preserves drag/interaction state
|
|
1410
1541
|
const updateOption: any = {
|
|
1411
1542
|
xAxis: currentOption.xAxis.map(() => ({ data: categoryData })),
|
|
1412
|
-
dataZoom: [
|
|
1413
|
-
|
|
1414
|
-
|
|
1415
|
-
],
|
|
1543
|
+
dataZoom: (currentOption.dataZoom || []).map(() => ({
|
|
1544
|
+
start: newStart, end: newEnd,
|
|
1545
|
+
})),
|
|
1416
1546
|
series: [
|
|
1417
1547
|
{ data: coloredCandlestickData, markLine: candlestickSeries.markLine },
|
|
1418
1548
|
...indicatorSeries.map((s) => {
|
|
@@ -1602,6 +1732,10 @@ export class QFChart implements ChartContext {
|
|
|
1602
1732
|
layout.separatePaneYAxisOffset, // Pass Y-axis offset for separate panes
|
|
1603
1733
|
);
|
|
1604
1734
|
|
|
1735
|
+
// Create hidden range-hint series so Pine Script drawing objects
|
|
1736
|
+
// (lines, boxes, labels, polylines) contribute to Y-axis auto-scaling.
|
|
1737
|
+
const drawingRangeHints = this._buildDrawingRangeHints(layout, paddingPoints);
|
|
1738
|
+
|
|
1605
1739
|
// Apply barColors (TradingView: barcolor() only changes body fill, borders/wicks stay default)
|
|
1606
1740
|
candlestickSeries.data = candlestickSeries.data.map((candle: any, i: number) => {
|
|
1607
1741
|
if (barColors[i]) {
|
|
@@ -1668,6 +1802,7 @@ export class QFChart implements ChartContext {
|
|
|
1668
1802
|
pixelPoints,
|
|
1669
1803
|
isSelected: drawing.id === this.selectedDrawingId,
|
|
1670
1804
|
api,
|
|
1805
|
+
coordSys: params.coordSys,
|
|
1671
1806
|
});
|
|
1672
1807
|
},
|
|
1673
1808
|
data: drawings.map((d) => {
|
|
@@ -1769,7 +1904,7 @@ export class QFChart implements ChartContext {
|
|
|
1769
1904
|
xAxis: layout.xAxis,
|
|
1770
1905
|
yAxis: layout.yAxis,
|
|
1771
1906
|
dataZoom: layout.dataZoom,
|
|
1772
|
-
series: [candlestickSeries, ...indicatorSeries, ...drawingSeriesList],
|
|
1907
|
+
series: [candlestickSeries, ...indicatorSeries, ...drawingRangeHints, ...drawingSeriesList],
|
|
1773
1908
|
};
|
|
1774
1909
|
|
|
1775
1910
|
this.chart.setOption(option, true); // true = not merge, replace.
|
|
@@ -268,6 +268,18 @@ export class LayoutManager {
|
|
|
268
268
|
|
|
269
269
|
let mainHeightVal = 75; // Default if no separate pane
|
|
270
270
|
|
|
271
|
+
// Parse layout.mainPaneHeight option (e.g. '40%' or 40)
|
|
272
|
+
let configuredMainHeight: number | undefined;
|
|
273
|
+
if (options.layout?.mainPaneHeight !== undefined) {
|
|
274
|
+
const raw = options.layout.mainPaneHeight;
|
|
275
|
+
if (typeof raw === 'string') {
|
|
276
|
+
const parsed = parseFloat(raw);
|
|
277
|
+
if (!isNaN(parsed)) configuredMainHeight = parsed;
|
|
278
|
+
} else if (typeof raw === 'number') {
|
|
279
|
+
configuredMainHeight = raw as unknown as number;
|
|
280
|
+
}
|
|
281
|
+
}
|
|
282
|
+
|
|
271
283
|
// Prepare separate panes configuration
|
|
272
284
|
let paneConfigs: PaneConfiguration[] = [];
|
|
273
285
|
|
|
@@ -286,33 +298,54 @@ export class LayoutManager {
|
|
|
286
298
|
};
|
|
287
299
|
});
|
|
288
300
|
|
|
289
|
-
// 2. Assign
|
|
290
|
-
|
|
291
|
-
const resolvedPanes = panes.map((p) => ({
|
|
301
|
+
// 2. Assign raw heights (collapsed = 3%, otherwise use requested or default 15)
|
|
302
|
+
const rawPanes = panes.map((p) => ({
|
|
292
303
|
...p,
|
|
293
|
-
|
|
304
|
+
rawHeight: p.isCollapsed ? 3 : p.requestedHeight !== undefined ? p.requestedHeight : 15,
|
|
294
305
|
}));
|
|
295
306
|
|
|
296
|
-
// 3. Calculate total space needed for indicators
|
|
297
|
-
const totalIndicatorHeight = resolvedPanes.reduce((sum, p) => sum + p.height, 0);
|
|
298
|
-
const totalGaps = resolvedPanes.length * gapPercent;
|
|
299
|
-
const totalBottomSpace = totalIndicatorHeight + totalGaps;
|
|
300
|
-
|
|
301
|
-
// 4. Calculate Main Chart Height
|
|
302
|
-
// Available space = chartAreaBottom - mainPaneTop;
|
|
303
307
|
const totalAvailable = chartAreaBottom - mainPaneTop;
|
|
304
|
-
|
|
308
|
+
const totalGaps = rawPanes.length * gapPercent;
|
|
305
309
|
|
|
306
|
-
//
|
|
310
|
+
// 4. Determine main chart height
|
|
307
311
|
if (mainHeightOverride !== undefined && mainHeightOverride > 0 && !isMainCollapsed) {
|
|
312
|
+
// Drag-resize takes absolute priority
|
|
308
313
|
mainHeightVal = mainHeightOverride;
|
|
309
314
|
} else if (isMainCollapsed) {
|
|
310
315
|
mainHeightVal = 3;
|
|
316
|
+
} else if (configuredMainHeight !== undefined && configuredMainHeight > 0) {
|
|
317
|
+
// User set mainPaneHeight — indicators fill remaining space proportionally
|
|
318
|
+
mainHeightVal = configuredMainHeight;
|
|
311
319
|
} else {
|
|
312
|
-
//
|
|
313
|
-
|
|
314
|
-
|
|
315
|
-
|
|
320
|
+
// Auto: subtract indicator heights from available space
|
|
321
|
+
const totalIndicatorHeight = rawPanes.reduce((sum, p) => sum + p.rawHeight, 0);
|
|
322
|
+
mainHeightVal = totalAvailable - totalIndicatorHeight - totalGaps;
|
|
323
|
+
if (mainHeightVal < 20) mainHeightVal = Math.max(mainHeightVal, 10);
|
|
324
|
+
}
|
|
325
|
+
|
|
326
|
+
// 3. Resolve indicator heights
|
|
327
|
+
// When mainPaneHeight is configured (or drag override active), distribute remaining space
|
|
328
|
+
// proportionally among non-collapsed panes using their rawHeight as weights.
|
|
329
|
+
const isMainHeightFixed = (mainHeightOverride !== undefined && mainHeightOverride > 0 && !isMainCollapsed)
|
|
330
|
+
|| (configuredMainHeight !== undefined && configuredMainHeight > 0 && !isMainCollapsed);
|
|
331
|
+
|
|
332
|
+
type ResolvedPane = (typeof rawPanes)[number] & { height: number };
|
|
333
|
+
let resolvedPanes: ResolvedPane[];
|
|
334
|
+
if (isMainHeightFixed) {
|
|
335
|
+
const remainingForIndicators = totalAvailable - mainHeightVal - totalGaps;
|
|
336
|
+
const totalWeights = rawPanes
|
|
337
|
+
.filter((p) => !p.isCollapsed)
|
|
338
|
+
.reduce((sum, p) => sum + p.rawHeight, 0);
|
|
339
|
+
resolvedPanes = rawPanes.map((p) => ({
|
|
340
|
+
...p,
|
|
341
|
+
height: p.isCollapsed
|
|
342
|
+
? 3
|
|
343
|
+
: totalWeights > 0
|
|
344
|
+
? Math.max(5, (p.rawHeight / totalWeights) * remainingForIndicators)
|
|
345
|
+
: remainingForIndicators / rawPanes.filter((x) => !x.isCollapsed).length,
|
|
346
|
+
}));
|
|
347
|
+
} else {
|
|
348
|
+
resolvedPanes = rawPanes.map((p) => ({ ...p, height: p.rawHeight }));
|
|
316
349
|
}
|
|
317
350
|
|
|
318
351
|
// 5. Calculate positions
|
|
@@ -332,6 +365,7 @@ export class LayoutManager {
|
|
|
332
365
|
return config;
|
|
333
366
|
});
|
|
334
367
|
} else {
|
|
368
|
+
// No secondary panes — mainPaneHeight is ignored, fill all available space
|
|
335
369
|
mainHeightVal = chartAreaBottom - mainPaneTop;
|
|
336
370
|
if (isMainCollapsed) {
|
|
337
371
|
mainHeightVal = 3;
|
|
@@ -384,7 +418,10 @@ export class LayoutManager {
|
|
|
384
418
|
const xAxis: any[] = [];
|
|
385
419
|
|
|
386
420
|
// Main X-Axis
|
|
421
|
+
// Hide date labels on the main chart when indicator panes exist below —
|
|
422
|
+
// the bottom-most pane's x-axis will show them instead.
|
|
387
423
|
const isMainBottom = paneConfigs.length === 0;
|
|
424
|
+
const showMainXLabels = !isMainCollapsed && isMainBottom;
|
|
388
425
|
xAxis.push({
|
|
389
426
|
type: 'category',
|
|
390
427
|
data: [], // Will be filled by SeriesBuilder or QFChart
|
|
@@ -401,7 +438,7 @@ export class LayoutManager {
|
|
|
401
438
|
lineStyle: { color: gridLineColor, opacity: gridLineOpacity },
|
|
402
439
|
},
|
|
403
440
|
axisLabel: {
|
|
404
|
-
show:
|
|
441
|
+
show: showMainXLabels,
|
|
405
442
|
color: '#94a3b8',
|
|
406
443
|
fontFamily: options.fontFamily || 'sans-serif',
|
|
407
444
|
formatter: (value: number) => {
|
|
@@ -413,7 +450,7 @@ export class LayoutManager {
|
|
|
413
450
|
return AxisUtils.formatValue(value, decimals);
|
|
414
451
|
},
|
|
415
452
|
},
|
|
416
|
-
axisTick: { show:
|
|
453
|
+
axisTick: { show: showMainXLabels },
|
|
417
454
|
axisPointer: {
|
|
418
455
|
label: {
|
|
419
456
|
show: isMainBottom,
|
|
@@ -424,15 +461,21 @@ export class LayoutManager {
|
|
|
424
461
|
});
|
|
425
462
|
|
|
426
463
|
// Separate Panes X-Axes
|
|
464
|
+
// Show date labels only on the bottom-most pane
|
|
427
465
|
paneConfigs.forEach((pane, i) => {
|
|
428
466
|
const isBottom = i === paneConfigs.length - 1;
|
|
467
|
+
const showLabels = isBottom && !pane.isCollapsed;
|
|
429
468
|
xAxis.push({
|
|
430
469
|
type: 'category',
|
|
431
470
|
gridIndex: i + 1, // 0 is main
|
|
432
471
|
data: [], // Shared data
|
|
433
|
-
axisLabel: {
|
|
472
|
+
axisLabel: {
|
|
473
|
+
show: showLabels,
|
|
474
|
+
color: '#94a3b8',
|
|
475
|
+
fontFamily: options.fontFamily || 'sans-serif',
|
|
476
|
+
},
|
|
434
477
|
axisLine: { show: !pane.isCollapsed && gridBorderShow, lineStyle: { color: gridBorderColor } },
|
|
435
|
-
axisTick: { show:
|
|
478
|
+
axisTick: { show: showLabels },
|
|
436
479
|
splitLine: { show: false },
|
|
437
480
|
axisPointer: {
|
|
438
481
|
label: {
|
|
@@ -515,16 +558,21 @@ export class LayoutManager {
|
|
|
515
558
|
const plotKey = `${id}::${plotName}`;
|
|
516
559
|
|
|
517
560
|
// Skip visual-only plot types that should never affect Y-axis scaling
|
|
518
|
-
// EXCEPTION: shapes with
|
|
519
|
-
const visualOnlyStyles = ['background', 'barcolor'
|
|
561
|
+
// EXCEPTION: shapes/chars with price-relative locations must stay on main Y-axis
|
|
562
|
+
const visualOnlyStyles = ['background', 'barcolor'];
|
|
520
563
|
|
|
521
|
-
// Check if this is a shape with price-relative positioning
|
|
564
|
+
// Check if this is a shape/char with price-relative positioning
|
|
565
|
+
// Includes abovebar/belowbar (relative to candle) and absolute (exact Y value)
|
|
522
566
|
const isShapeWithPriceLocation =
|
|
523
|
-
plot.options.style === 'shape' &&
|
|
567
|
+
(plot.options.style === 'shape' || plot.options.style === 'char') &&
|
|
524
568
|
(plot.options.location === 'abovebar' ||
|
|
525
569
|
plot.options.location === 'AboveBar' ||
|
|
570
|
+
plot.options.location === 'ab' ||
|
|
526
571
|
plot.options.location === 'belowbar' ||
|
|
527
|
-
plot.options.location === 'BelowBar'
|
|
572
|
+
plot.options.location === 'BelowBar' ||
|
|
573
|
+
plot.options.location === 'bl' ||
|
|
574
|
+
plot.options.location === 'absolute' ||
|
|
575
|
+
plot.options.location === 'Absolute');
|
|
528
576
|
|
|
529
577
|
if (visualOnlyStyles.includes(plot.options.style)) {
|
|
530
578
|
// Assign these to a separate Y-axis so they don't affect price scale
|
|
@@ -535,8 +583,8 @@ export class LayoutManager {
|
|
|
535
583
|
return; // Skip further processing for this plot
|
|
536
584
|
}
|
|
537
585
|
|
|
538
|
-
// If it's a shape but NOT with price-relative positioning, treat as visual-only
|
|
539
|
-
if (plot.options.style === 'shape' && !isShapeWithPriceLocation) {
|
|
586
|
+
// If it's a shape/char but NOT with price-relative positioning, treat as visual-only
|
|
587
|
+
if ((plot.options.style === 'shape' || plot.options.style === 'char') && !isShapeWithPriceLocation) {
|
|
540
588
|
if (!overlayYAxisMap.has(plotKey)) {
|
|
541
589
|
overlayYAxisMap.set(plotKey, nextYAxisIndex);
|
|
542
590
|
nextYAxisIndex++;
|