hyperprop-charting-library 0.1.21 → 0.1.22
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/README.md +50 -0
- package/dist/hyperprop-charting-library.cjs +13 -7
- package/dist/hyperprop-charting-library.d.ts +1 -0
- package/dist/hyperprop-charting-library.js +13 -7
- package/dist/index.cjs +13 -7
- package/dist/index.d.cts +1 -0
- package/dist/index.d.ts +1 -0
- package/dist/index.js +13 -7
- package/docs/API.md +36 -1
- package/docs/RECIPES.md +61 -0
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -76,6 +76,55 @@ chart.onCrosshairPriceAction((event) => {
|
|
|
76
76
|
});
|
|
77
77
|
```
|
|
78
78
|
|
|
79
|
+
## Built-In Indicators
|
|
80
|
+
|
|
81
|
+
Built-ins are part of this package (no extra install):
|
|
82
|
+
|
|
83
|
+
- `volume`
|
|
84
|
+
- `sma`
|
|
85
|
+
- `ema`
|
|
86
|
+
- `rsi`
|
|
87
|
+
- `wma`
|
|
88
|
+
- `vwma`
|
|
89
|
+
- `rma`
|
|
90
|
+
- `hma`
|
|
91
|
+
- `stddev`
|
|
92
|
+
- `atr`
|
|
93
|
+
|
|
94
|
+
Quick usage:
|
|
95
|
+
|
|
96
|
+
```ts
|
|
97
|
+
const emaId = chart.addIndicator("ema", { length: 34, source: "close" });
|
|
98
|
+
const volumeId = chart.addIndicator("volume", { upOpacity: 0.72, downOpacity: 0.72 }, { paneHeightRatio: 0.16 });
|
|
99
|
+
|
|
100
|
+
// update any indicator instance
|
|
101
|
+
chart.updateIndicator(emaId, { inputs: { length: 55 } });
|
|
102
|
+
chart.updateIndicator(volumeId, { paneHeightRatio: 0.1 });
|
|
103
|
+
```
|
|
104
|
+
|
|
105
|
+
Volume note:
|
|
106
|
+
|
|
107
|
+
- `volume` and `vwma` require `OhlcDataPoint.v` for best results.
|
|
108
|
+
|
|
109
|
+
## Custom Indicator Plugins
|
|
110
|
+
|
|
111
|
+
You can register your own indicators in-app:
|
|
112
|
+
|
|
113
|
+
```ts
|
|
114
|
+
chart.registerIndicator({
|
|
115
|
+
id: "my-line",
|
|
116
|
+
name: "My Line",
|
|
117
|
+
pane: "overlay",
|
|
118
|
+
defaultInputs: { color: "#22d3ee", width: 2 },
|
|
119
|
+
draw: (ctx, rc, inputs) => {
|
|
120
|
+
if (!rc.yFromPrice) return;
|
|
121
|
+
// draw using rc.xFromIndex(...) and rc.yFromPrice(...)
|
|
122
|
+
}
|
|
123
|
+
});
|
|
124
|
+
|
|
125
|
+
const myId = chart.addIndicator("my-line");
|
|
126
|
+
```
|
|
127
|
+
|
|
79
128
|
## Full Documentation
|
|
80
129
|
|
|
81
130
|
- API reference: `docs/API.md`
|
|
@@ -92,6 +141,7 @@ chart.onCrosshairPriceAction((event) => {
|
|
|
92
141
|
- `chart.setOrderLines(lines)` / `chart.addOrderLine(line)` / `chart.updateOrderLine(id, patch)` / `chart.removeOrderLine(id)`
|
|
93
142
|
- `chart.onOrderAction(handler)` / `chart.onChartClick(handler)` / `chart.onCrosshairMove(handler)` / `chart.onCrosshairPriceAction(handler)`
|
|
94
143
|
- `chart.setDoubleClickEnabled(enabled)` / `chart.setDoubleClickAction(action)`
|
|
144
|
+
- `chart.registerIndicator(plugin)` / `chart.addIndicator(type, inputs?, options?)` / `chart.updateIndicator(id, patch)` / `chart.removeIndicator(id)`
|
|
95
145
|
- `chart.zoomInX()` / `chart.zoomOutX()` / `chart.panX(bars)` / `chart.resetViewport()`
|
|
96
146
|
- `chart.resize(width, height)` / `chart.destroy()`
|
|
97
147
|
|
|
@@ -478,16 +478,21 @@ var drawSeparateSeries = (ctx, renderContext, values, color, width, minOverride,
|
|
|
478
478
|
var BUILTIN_VOLUME_INDICATOR = {
|
|
479
479
|
id: "volume",
|
|
480
480
|
name: "Volume",
|
|
481
|
-
pane: "
|
|
481
|
+
pane: "overlay",
|
|
482
482
|
paneHeightRatio: 0.22,
|
|
483
483
|
defaultInputs: {
|
|
484
484
|
upOpacity: 0.7,
|
|
485
485
|
downOpacity: 0.7,
|
|
486
|
-
minBarWidth: 1
|
|
486
|
+
minBarWidth: 1,
|
|
487
|
+
overlayHeightRatio: 0.22
|
|
487
488
|
},
|
|
488
489
|
draw: (ctx, renderContext, inputs) => {
|
|
489
490
|
const { data, startIndex, endIndex, chartTop, chartBottom, candleSpacing, xFromIndex, upColor, downColor } = renderContext;
|
|
490
|
-
const
|
|
491
|
+
const fullPaneHeight = Math.max(1, chartBottom - chartTop);
|
|
492
|
+
const overlayMode = renderContext.yFromPrice !== null;
|
|
493
|
+
const overlayHeightRatio = Math.min(0.45, Math.max(0.08, Number(inputs.overlayHeightRatio) || 0.22));
|
|
494
|
+
const paneHeight = overlayMode ? Math.max(20, Math.round(fullPaneHeight * overlayHeightRatio)) : fullPaneHeight;
|
|
495
|
+
const paneBottom = chartBottom;
|
|
491
496
|
const visiblePoints = data.slice(startIndex, endIndex + 1);
|
|
492
497
|
const maxVolume = Math.max(1, ...visiblePoints.map((point) => point.v ?? 0));
|
|
493
498
|
const barWidth = Math.max(
|
|
@@ -505,7 +510,7 @@ var BUILTIN_VOLUME_INDICATOR = {
|
|
|
505
510
|
const volumeHeight = Math.max(1, Math.round(paneHeight * ratio));
|
|
506
511
|
const xCenter = xFromIndex(index);
|
|
507
512
|
const barX = Math.round(xCenter - barWidth / 2);
|
|
508
|
-
const barY = Math.round(
|
|
513
|
+
const barY = Math.round(paneBottom - volumeHeight);
|
|
509
514
|
const isUp = point.c >= point.o;
|
|
510
515
|
const opacity = isUp ? upOpacity : downOpacity;
|
|
511
516
|
ctx.save();
|
|
@@ -702,6 +707,7 @@ function createChart(element, options = {}) {
|
|
|
702
707
|
type: indicator.type,
|
|
703
708
|
visible: indicator.visible ?? true,
|
|
704
709
|
pane: indicator.pane ?? plugin?.pane ?? "overlay",
|
|
710
|
+
...indicator.paneHeightRatio === void 0 ? {} : { paneHeightRatio: indicator.paneHeightRatio },
|
|
705
711
|
inputs: {
|
|
706
712
|
...defaults,
|
|
707
713
|
...indicator.inputs ?? {}
|
|
@@ -1360,8 +1366,8 @@ function createChart(element, options = {}) {
|
|
|
1360
1366
|
const activeSeparateIndicators = indicators.filter((indicator) => indicator.visible).map((indicator) => ({ indicator, plugin: indicatorRegistry.get(indicator.type) })).filter(
|
|
1361
1367
|
(value) => value.plugin !== void 0 && (value.indicator.pane ?? value.plugin.pane ?? "overlay") === "separate"
|
|
1362
1368
|
);
|
|
1363
|
-
const separatePaneHeightDefaults = activeSeparateIndicators.map(({ plugin }) => {
|
|
1364
|
-
const ratio = Math.min(0.45, Math.max(0.08, plugin.paneHeightRatio ?? 0.22));
|
|
1369
|
+
const separatePaneHeightDefaults = activeSeparateIndicators.map(({ indicator, plugin }) => {
|
|
1370
|
+
const ratio = Math.min(0.45, Math.max(0.08, indicator.paneHeightRatio ?? plugin.paneHeightRatio ?? 0.22));
|
|
1365
1371
|
return Math.round(fullChartHeight * ratio);
|
|
1366
1372
|
});
|
|
1367
1373
|
const separatePaneDesiredTotal = separatePaneHeightDefaults.reduce((sum, value) => sum + value, 0) + Math.max(0, activeSeparateIndicators.length - 1) * separatePaneSpacing;
|
|
@@ -1665,7 +1671,6 @@ function createChart(element, options = {}) {
|
|
|
1665
1671
|
ctx.lineTo(crisp(chartRight), crisp(paneTop));
|
|
1666
1672
|
ctx.stroke();
|
|
1667
1673
|
ctx.restore();
|
|
1668
|
-
drawText(plugin.name.toUpperCase(), chartLeft + 6, paneTop + 12, "left", "middle", axis.textColor);
|
|
1669
1674
|
});
|
|
1670
1675
|
}
|
|
1671
1676
|
ctx.strokeStyle = axis.lineColor;
|
|
@@ -2535,6 +2540,7 @@ function createChart(element, options = {}) {
|
|
|
2535
2540
|
...indicator,
|
|
2536
2541
|
visible: patch.visible ?? indicator.visible,
|
|
2537
2542
|
pane: patch.pane ?? indicator.pane ?? plugin?.pane ?? "overlay",
|
|
2543
|
+
...patch.paneHeightRatio !== void 0 || indicator.paneHeightRatio !== void 0 ? { paneHeightRatio: patch.paneHeightRatio ?? indicator.paneHeightRatio } : {},
|
|
2538
2544
|
type: patch.type ?? indicator.type,
|
|
2539
2545
|
inputs: {
|
|
2540
2546
|
...indicator.inputs,
|
|
@@ -454,16 +454,21 @@ var drawSeparateSeries = (ctx, renderContext, values, color, width, minOverride,
|
|
|
454
454
|
var BUILTIN_VOLUME_INDICATOR = {
|
|
455
455
|
id: "volume",
|
|
456
456
|
name: "Volume",
|
|
457
|
-
pane: "
|
|
457
|
+
pane: "overlay",
|
|
458
458
|
paneHeightRatio: 0.22,
|
|
459
459
|
defaultInputs: {
|
|
460
460
|
upOpacity: 0.7,
|
|
461
461
|
downOpacity: 0.7,
|
|
462
|
-
minBarWidth: 1
|
|
462
|
+
minBarWidth: 1,
|
|
463
|
+
overlayHeightRatio: 0.22
|
|
463
464
|
},
|
|
464
465
|
draw: (ctx, renderContext, inputs) => {
|
|
465
466
|
const { data, startIndex, endIndex, chartTop, chartBottom, candleSpacing, xFromIndex, upColor, downColor } = renderContext;
|
|
466
|
-
const
|
|
467
|
+
const fullPaneHeight = Math.max(1, chartBottom - chartTop);
|
|
468
|
+
const overlayMode = renderContext.yFromPrice !== null;
|
|
469
|
+
const overlayHeightRatio = Math.min(0.45, Math.max(0.08, Number(inputs.overlayHeightRatio) || 0.22));
|
|
470
|
+
const paneHeight = overlayMode ? Math.max(20, Math.round(fullPaneHeight * overlayHeightRatio)) : fullPaneHeight;
|
|
471
|
+
const paneBottom = chartBottom;
|
|
467
472
|
const visiblePoints = data.slice(startIndex, endIndex + 1);
|
|
468
473
|
const maxVolume = Math.max(1, ...visiblePoints.map((point) => point.v ?? 0));
|
|
469
474
|
const barWidth = Math.max(
|
|
@@ -481,7 +486,7 @@ var BUILTIN_VOLUME_INDICATOR = {
|
|
|
481
486
|
const volumeHeight = Math.max(1, Math.round(paneHeight * ratio));
|
|
482
487
|
const xCenter = xFromIndex(index);
|
|
483
488
|
const barX = Math.round(xCenter - barWidth / 2);
|
|
484
|
-
const barY = Math.round(
|
|
489
|
+
const barY = Math.round(paneBottom - volumeHeight);
|
|
485
490
|
const isUp = point.c >= point.o;
|
|
486
491
|
const opacity = isUp ? upOpacity : downOpacity;
|
|
487
492
|
ctx.save();
|
|
@@ -678,6 +683,7 @@ function createChart(element, options = {}) {
|
|
|
678
683
|
type: indicator.type,
|
|
679
684
|
visible: indicator.visible ?? true,
|
|
680
685
|
pane: indicator.pane ?? plugin?.pane ?? "overlay",
|
|
686
|
+
...indicator.paneHeightRatio === void 0 ? {} : { paneHeightRatio: indicator.paneHeightRatio },
|
|
681
687
|
inputs: {
|
|
682
688
|
...defaults,
|
|
683
689
|
...indicator.inputs ?? {}
|
|
@@ -1336,8 +1342,8 @@ function createChart(element, options = {}) {
|
|
|
1336
1342
|
const activeSeparateIndicators = indicators.filter((indicator) => indicator.visible).map((indicator) => ({ indicator, plugin: indicatorRegistry.get(indicator.type) })).filter(
|
|
1337
1343
|
(value) => value.plugin !== void 0 && (value.indicator.pane ?? value.plugin.pane ?? "overlay") === "separate"
|
|
1338
1344
|
);
|
|
1339
|
-
const separatePaneHeightDefaults = activeSeparateIndicators.map(({ plugin }) => {
|
|
1340
|
-
const ratio = Math.min(0.45, Math.max(0.08, plugin.paneHeightRatio ?? 0.22));
|
|
1345
|
+
const separatePaneHeightDefaults = activeSeparateIndicators.map(({ indicator, plugin }) => {
|
|
1346
|
+
const ratio = Math.min(0.45, Math.max(0.08, indicator.paneHeightRatio ?? plugin.paneHeightRatio ?? 0.22));
|
|
1341
1347
|
return Math.round(fullChartHeight * ratio);
|
|
1342
1348
|
});
|
|
1343
1349
|
const separatePaneDesiredTotal = separatePaneHeightDefaults.reduce((sum, value) => sum + value, 0) + Math.max(0, activeSeparateIndicators.length - 1) * separatePaneSpacing;
|
|
@@ -1641,7 +1647,6 @@ function createChart(element, options = {}) {
|
|
|
1641
1647
|
ctx.lineTo(crisp(chartRight), crisp(paneTop));
|
|
1642
1648
|
ctx.stroke();
|
|
1643
1649
|
ctx.restore();
|
|
1644
|
-
drawText(plugin.name.toUpperCase(), chartLeft + 6, paneTop + 12, "left", "middle", axis.textColor);
|
|
1645
1650
|
});
|
|
1646
1651
|
}
|
|
1647
1652
|
ctx.strokeStyle = axis.lineColor;
|
|
@@ -2511,6 +2516,7 @@ function createChart(element, options = {}) {
|
|
|
2511
2516
|
...indicator,
|
|
2512
2517
|
visible: patch.visible ?? indicator.visible,
|
|
2513
2518
|
pane: patch.pane ?? indicator.pane ?? plugin?.pane ?? "overlay",
|
|
2519
|
+
...patch.paneHeightRatio !== void 0 || indicator.paneHeightRatio !== void 0 ? { paneHeightRatio: patch.paneHeightRatio ?? indicator.paneHeightRatio } : {},
|
|
2514
2520
|
type: patch.type ?? indicator.type,
|
|
2515
2521
|
inputs: {
|
|
2516
2522
|
...indicator.inputs,
|
package/dist/index.cjs
CHANGED
|
@@ -478,16 +478,21 @@ var drawSeparateSeries = (ctx, renderContext, values, color, width, minOverride,
|
|
|
478
478
|
var BUILTIN_VOLUME_INDICATOR = {
|
|
479
479
|
id: "volume",
|
|
480
480
|
name: "Volume",
|
|
481
|
-
pane: "
|
|
481
|
+
pane: "overlay",
|
|
482
482
|
paneHeightRatio: 0.22,
|
|
483
483
|
defaultInputs: {
|
|
484
484
|
upOpacity: 0.7,
|
|
485
485
|
downOpacity: 0.7,
|
|
486
|
-
minBarWidth: 1
|
|
486
|
+
minBarWidth: 1,
|
|
487
|
+
overlayHeightRatio: 0.22
|
|
487
488
|
},
|
|
488
489
|
draw: (ctx, renderContext, inputs) => {
|
|
489
490
|
const { data, startIndex, endIndex, chartTop, chartBottom, candleSpacing, xFromIndex, upColor, downColor } = renderContext;
|
|
490
|
-
const
|
|
491
|
+
const fullPaneHeight = Math.max(1, chartBottom - chartTop);
|
|
492
|
+
const overlayMode = renderContext.yFromPrice !== null;
|
|
493
|
+
const overlayHeightRatio = Math.min(0.45, Math.max(0.08, Number(inputs.overlayHeightRatio) || 0.22));
|
|
494
|
+
const paneHeight = overlayMode ? Math.max(20, Math.round(fullPaneHeight * overlayHeightRatio)) : fullPaneHeight;
|
|
495
|
+
const paneBottom = chartBottom;
|
|
491
496
|
const visiblePoints = data.slice(startIndex, endIndex + 1);
|
|
492
497
|
const maxVolume = Math.max(1, ...visiblePoints.map((point) => point.v ?? 0));
|
|
493
498
|
const barWidth = Math.max(
|
|
@@ -505,7 +510,7 @@ var BUILTIN_VOLUME_INDICATOR = {
|
|
|
505
510
|
const volumeHeight = Math.max(1, Math.round(paneHeight * ratio));
|
|
506
511
|
const xCenter = xFromIndex(index);
|
|
507
512
|
const barX = Math.round(xCenter - barWidth / 2);
|
|
508
|
-
const barY = Math.round(
|
|
513
|
+
const barY = Math.round(paneBottom - volumeHeight);
|
|
509
514
|
const isUp = point.c >= point.o;
|
|
510
515
|
const opacity = isUp ? upOpacity : downOpacity;
|
|
511
516
|
ctx.save();
|
|
@@ -702,6 +707,7 @@ function createChart(element, options = {}) {
|
|
|
702
707
|
type: indicator.type,
|
|
703
708
|
visible: indicator.visible ?? true,
|
|
704
709
|
pane: indicator.pane ?? plugin?.pane ?? "overlay",
|
|
710
|
+
...indicator.paneHeightRatio === void 0 ? {} : { paneHeightRatio: indicator.paneHeightRatio },
|
|
705
711
|
inputs: {
|
|
706
712
|
...defaults,
|
|
707
713
|
...indicator.inputs ?? {}
|
|
@@ -1360,8 +1366,8 @@ function createChart(element, options = {}) {
|
|
|
1360
1366
|
const activeSeparateIndicators = indicators.filter((indicator) => indicator.visible).map((indicator) => ({ indicator, plugin: indicatorRegistry.get(indicator.type) })).filter(
|
|
1361
1367
|
(value) => value.plugin !== void 0 && (value.indicator.pane ?? value.plugin.pane ?? "overlay") === "separate"
|
|
1362
1368
|
);
|
|
1363
|
-
const separatePaneHeightDefaults = activeSeparateIndicators.map(({ plugin }) => {
|
|
1364
|
-
const ratio = Math.min(0.45, Math.max(0.08, plugin.paneHeightRatio ?? 0.22));
|
|
1369
|
+
const separatePaneHeightDefaults = activeSeparateIndicators.map(({ indicator, plugin }) => {
|
|
1370
|
+
const ratio = Math.min(0.45, Math.max(0.08, indicator.paneHeightRatio ?? plugin.paneHeightRatio ?? 0.22));
|
|
1365
1371
|
return Math.round(fullChartHeight * ratio);
|
|
1366
1372
|
});
|
|
1367
1373
|
const separatePaneDesiredTotal = separatePaneHeightDefaults.reduce((sum, value) => sum + value, 0) + Math.max(0, activeSeparateIndicators.length - 1) * separatePaneSpacing;
|
|
@@ -1665,7 +1671,6 @@ function createChart(element, options = {}) {
|
|
|
1665
1671
|
ctx.lineTo(crisp(chartRight), crisp(paneTop));
|
|
1666
1672
|
ctx.stroke();
|
|
1667
1673
|
ctx.restore();
|
|
1668
|
-
drawText(plugin.name.toUpperCase(), chartLeft + 6, paneTop + 12, "left", "middle", axis.textColor);
|
|
1669
1674
|
});
|
|
1670
1675
|
}
|
|
1671
1676
|
ctx.strokeStyle = axis.lineColor;
|
|
@@ -2535,6 +2540,7 @@ function createChart(element, options = {}) {
|
|
|
2535
2540
|
...indicator,
|
|
2536
2541
|
visible: patch.visible ?? indicator.visible,
|
|
2537
2542
|
pane: patch.pane ?? indicator.pane ?? plugin?.pane ?? "overlay",
|
|
2543
|
+
...patch.paneHeightRatio !== void 0 || indicator.paneHeightRatio !== void 0 ? { paneHeightRatio: patch.paneHeightRatio ?? indicator.paneHeightRatio } : {},
|
|
2538
2544
|
type: patch.type ?? indicator.type,
|
|
2539
2545
|
inputs: {
|
|
2540
2546
|
...indicator.inputs,
|
package/dist/index.d.cts
CHANGED
package/dist/index.d.ts
CHANGED
package/dist/index.js
CHANGED
|
@@ -454,16 +454,21 @@ var drawSeparateSeries = (ctx, renderContext, values, color, width, minOverride,
|
|
|
454
454
|
var BUILTIN_VOLUME_INDICATOR = {
|
|
455
455
|
id: "volume",
|
|
456
456
|
name: "Volume",
|
|
457
|
-
pane: "
|
|
457
|
+
pane: "overlay",
|
|
458
458
|
paneHeightRatio: 0.22,
|
|
459
459
|
defaultInputs: {
|
|
460
460
|
upOpacity: 0.7,
|
|
461
461
|
downOpacity: 0.7,
|
|
462
|
-
minBarWidth: 1
|
|
462
|
+
minBarWidth: 1,
|
|
463
|
+
overlayHeightRatio: 0.22
|
|
463
464
|
},
|
|
464
465
|
draw: (ctx, renderContext, inputs) => {
|
|
465
466
|
const { data, startIndex, endIndex, chartTop, chartBottom, candleSpacing, xFromIndex, upColor, downColor } = renderContext;
|
|
466
|
-
const
|
|
467
|
+
const fullPaneHeight = Math.max(1, chartBottom - chartTop);
|
|
468
|
+
const overlayMode = renderContext.yFromPrice !== null;
|
|
469
|
+
const overlayHeightRatio = Math.min(0.45, Math.max(0.08, Number(inputs.overlayHeightRatio) || 0.22));
|
|
470
|
+
const paneHeight = overlayMode ? Math.max(20, Math.round(fullPaneHeight * overlayHeightRatio)) : fullPaneHeight;
|
|
471
|
+
const paneBottom = chartBottom;
|
|
467
472
|
const visiblePoints = data.slice(startIndex, endIndex + 1);
|
|
468
473
|
const maxVolume = Math.max(1, ...visiblePoints.map((point) => point.v ?? 0));
|
|
469
474
|
const barWidth = Math.max(
|
|
@@ -481,7 +486,7 @@ var BUILTIN_VOLUME_INDICATOR = {
|
|
|
481
486
|
const volumeHeight = Math.max(1, Math.round(paneHeight * ratio));
|
|
482
487
|
const xCenter = xFromIndex(index);
|
|
483
488
|
const barX = Math.round(xCenter - barWidth / 2);
|
|
484
|
-
const barY = Math.round(
|
|
489
|
+
const barY = Math.round(paneBottom - volumeHeight);
|
|
485
490
|
const isUp = point.c >= point.o;
|
|
486
491
|
const opacity = isUp ? upOpacity : downOpacity;
|
|
487
492
|
ctx.save();
|
|
@@ -678,6 +683,7 @@ function createChart(element, options = {}) {
|
|
|
678
683
|
type: indicator.type,
|
|
679
684
|
visible: indicator.visible ?? true,
|
|
680
685
|
pane: indicator.pane ?? plugin?.pane ?? "overlay",
|
|
686
|
+
...indicator.paneHeightRatio === void 0 ? {} : { paneHeightRatio: indicator.paneHeightRatio },
|
|
681
687
|
inputs: {
|
|
682
688
|
...defaults,
|
|
683
689
|
...indicator.inputs ?? {}
|
|
@@ -1336,8 +1342,8 @@ function createChart(element, options = {}) {
|
|
|
1336
1342
|
const activeSeparateIndicators = indicators.filter((indicator) => indicator.visible).map((indicator) => ({ indicator, plugin: indicatorRegistry.get(indicator.type) })).filter(
|
|
1337
1343
|
(value) => value.plugin !== void 0 && (value.indicator.pane ?? value.plugin.pane ?? "overlay") === "separate"
|
|
1338
1344
|
);
|
|
1339
|
-
const separatePaneHeightDefaults = activeSeparateIndicators.map(({ plugin }) => {
|
|
1340
|
-
const ratio = Math.min(0.45, Math.max(0.08, plugin.paneHeightRatio ?? 0.22));
|
|
1345
|
+
const separatePaneHeightDefaults = activeSeparateIndicators.map(({ indicator, plugin }) => {
|
|
1346
|
+
const ratio = Math.min(0.45, Math.max(0.08, indicator.paneHeightRatio ?? plugin.paneHeightRatio ?? 0.22));
|
|
1341
1347
|
return Math.round(fullChartHeight * ratio);
|
|
1342
1348
|
});
|
|
1343
1349
|
const separatePaneDesiredTotal = separatePaneHeightDefaults.reduce((sum, value) => sum + value, 0) + Math.max(0, activeSeparateIndicators.length - 1) * separatePaneSpacing;
|
|
@@ -1641,7 +1647,6 @@ function createChart(element, options = {}) {
|
|
|
1641
1647
|
ctx.lineTo(crisp(chartRight), crisp(paneTop));
|
|
1642
1648
|
ctx.stroke();
|
|
1643
1649
|
ctx.restore();
|
|
1644
|
-
drawText(plugin.name.toUpperCase(), chartLeft + 6, paneTop + 12, "left", "middle", axis.textColor);
|
|
1645
1650
|
});
|
|
1646
1651
|
}
|
|
1647
1652
|
ctx.strokeStyle = axis.lineColor;
|
|
@@ -2511,6 +2516,7 @@ function createChart(element, options = {}) {
|
|
|
2511
2516
|
...indicator,
|
|
2512
2517
|
visible: patch.visible ?? indicator.visible,
|
|
2513
2518
|
pane: patch.pane ?? indicator.pane ?? plugin?.pane ?? "overlay",
|
|
2519
|
+
...patch.paneHeightRatio !== void 0 || indicator.paneHeightRatio !== void 0 ? { paneHeightRatio: patch.paneHeightRatio ?? indicator.paneHeightRatio } : {},
|
|
2514
2520
|
type: patch.type ?? indicator.type,
|
|
2515
2521
|
inputs: {
|
|
2516
2522
|
...indicator.inputs,
|
package/docs/API.md
CHANGED
|
@@ -245,6 +245,7 @@ Connector/fill visuals:
|
|
|
245
245
|
- `type: string` (registered indicator id, e.g. `"volume"`)
|
|
246
246
|
- `visible?: boolean` (default `true`)
|
|
247
247
|
- `pane?: "overlay" | "separate"`
|
|
248
|
+
- `paneHeightRatio?: number` (for separate panes; `0.08` to `0.45` recommended)
|
|
248
249
|
- `inputs?: Record<string, unknown>` (plugin-specific parameters)
|
|
249
250
|
|
|
250
251
|
### `IndicatorPlugin`
|
|
@@ -256,9 +257,43 @@ Connector/fill visuals:
|
|
|
256
257
|
- `defaultInputs?: Record<string, unknown>`
|
|
257
258
|
- `draw(ctx, renderContext, inputs): void`
|
|
258
259
|
|
|
260
|
+
`IndicatorRenderContext` includes:
|
|
261
|
+
|
|
262
|
+
- shared x-window and visible index range (`startIndex`, `endIndex`, `xStart`, `xSpan`)
|
|
263
|
+
- pane bounds (`chartLeft`, `chartRight`, `chartTop`, `chartBottom`, `chartWidth`, `chartHeight`)
|
|
264
|
+
- `xFromIndex(index)` helper
|
|
265
|
+
- `yFromPrice(price)` helper (available for overlay indicators, `null` for separate-pane indicators)
|
|
266
|
+
- theme colors (`upColor`, `downColor`)
|
|
267
|
+
|
|
259
268
|
Built-in:
|
|
260
269
|
|
|
261
|
-
- `"volume"`:
|
|
270
|
+
- `"volume"`: separate pane histogram (uses `OhlcDataPoint.v`)
|
|
271
|
+
- `"sma"`: Simple Moving Average (overlay)
|
|
272
|
+
- `"ema"`: Exponential Moving Average (overlay)
|
|
273
|
+
- `"rsi"`: Relative Strength Index (separate pane, 30/50/70 guides)
|
|
274
|
+
- `"wma"`: Weighted Moving Average (overlay)
|
|
275
|
+
- `"vwma"`: Volume Weighted Moving Average (overlay, uses `OhlcDataPoint.v`)
|
|
276
|
+
- `"rma"`: Wilder's Moving Average (overlay)
|
|
277
|
+
- `"hma"`: Hull Moving Average (overlay)
|
|
278
|
+
- `"stddev"`: Standard Deviation (separate pane)
|
|
279
|
+
- `"atr"`: Average True Range (separate pane)
|
|
280
|
+
|
|
281
|
+
Example:
|
|
282
|
+
|
|
283
|
+
```ts
|
|
284
|
+
const emaId = chart.addIndicator("ema", { length: 34, source: "close" });
|
|
285
|
+
const rsiId = chart.addIndicator("rsi", { length: 14 }, { pane: "separate", paneHeightRatio: 0.18 });
|
|
286
|
+
|
|
287
|
+
// resize pane later (useful for drag-handle UX in frontend)
|
|
288
|
+
chart.updateIndicator(rsiId, { paneHeightRatio: 0.12 });
|
|
289
|
+
|
|
290
|
+
// remove when needed
|
|
291
|
+
chart.removeIndicator(emaId);
|
|
292
|
+
```
|
|
293
|
+
|
|
294
|
+
Volume/VWMA note:
|
|
295
|
+
|
|
296
|
+
- if your data has no `v`, `volume`/`vwma` will have limited or no output.
|
|
262
297
|
|
|
263
298
|
---
|
|
264
299
|
|
package/docs/RECIPES.md
CHANGED
|
@@ -91,6 +91,67 @@ const chart = createChart(root, {
|
|
|
91
91
|
});
|
|
92
92
|
```
|
|
93
93
|
|
|
94
|
+
## Add built-in indicators
|
|
95
|
+
|
|
96
|
+
```ts
|
|
97
|
+
const emaId = chart.addIndicator("ema", { length: 34, source: "close" });
|
|
98
|
+
const rsiId = chart.addIndicator("rsi", { length: 14 }, { pane: "separate", paneHeightRatio: 0.18 });
|
|
99
|
+
const volumeId = chart.addIndicator("volume", { upOpacity: 0.72, downOpacity: 0.72 });
|
|
100
|
+
```
|
|
101
|
+
|
|
102
|
+
Available built-ins:
|
|
103
|
+
|
|
104
|
+
- `volume`, `sma`, `ema`, `rsi`, `wma`, `vwma`, `rma`, `hma`, `stddev`, `atr`
|
|
105
|
+
|
|
106
|
+
## Resize indicator pane from frontend
|
|
107
|
+
|
|
108
|
+
Use `paneHeightRatio` so your app can wire a drag handle or slider:
|
|
109
|
+
|
|
110
|
+
```ts
|
|
111
|
+
const volumeId = chart.addIndicator("volume", {}, { paneHeightRatio: 0.2 });
|
|
112
|
+
|
|
113
|
+
// e.g. on slider/drag updates:
|
|
114
|
+
chart.updateIndicator(volumeId, { paneHeightRatio: 0.1 });
|
|
115
|
+
```
|
|
116
|
+
|
|
117
|
+
Recommended range:
|
|
118
|
+
|
|
119
|
+
- `0.08` to `0.45` (library clamps to safe bounds)
|
|
120
|
+
|
|
121
|
+
## Remove or hide indicators
|
|
122
|
+
|
|
123
|
+
```ts
|
|
124
|
+
const id = chart.addIndicator("ema", { length: 21 });
|
|
125
|
+
chart.updateIndicator(id, { visible: false }); // hide
|
|
126
|
+
chart.removeIndicator(id); // remove
|
|
127
|
+
```
|
|
128
|
+
|
|
129
|
+
## Volume and VWMA data requirement
|
|
130
|
+
|
|
131
|
+
`volume` and `vwma` use `OhlcDataPoint.v`. If `v` is missing, output can be empty/limited.
|
|
132
|
+
|
|
133
|
+
```ts
|
|
134
|
+
const data = [
|
|
135
|
+
{ t: "2026-01-01T00:00:00Z", o: 100, h: 103, l: 99, c: 102, v: 125000 }
|
|
136
|
+
];
|
|
137
|
+
chart.setData(data);
|
|
138
|
+
```
|
|
139
|
+
|
|
140
|
+
## Register a custom indicator plugin
|
|
141
|
+
|
|
142
|
+
```ts
|
|
143
|
+
chart.registerIndicator({
|
|
144
|
+
id: "my-overlay-line",
|
|
145
|
+
name: "My Overlay Line",
|
|
146
|
+
pane: "overlay",
|
|
147
|
+
defaultInputs: { color: "#22d3ee", width: 2 },
|
|
148
|
+
draw: (ctx, rc, inputs) => {
|
|
149
|
+
if (!rc.yFromPrice) return;
|
|
150
|
+
// draw custom line with rc.xFromIndex + rc.yFromPrice
|
|
151
|
+
}
|
|
152
|
+
});
|
|
153
|
+
```
|
|
154
|
+
|
|
94
155
|
## Add a draggable pending limit line
|
|
95
156
|
|
|
96
157
|
```ts
|