hyperprop-charting-library 0.1.26 → 0.1.28
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 +12 -0
- package/dist/hyperprop-charting-library.cjs +26 -6
- package/dist/hyperprop-charting-library.d.ts +2 -0
- package/dist/hyperprop-charting-library.js +26 -6
- package/dist/index.cjs +26 -6
- package/dist/index.d.cts +2 -0
- package/dist/index.d.ts +2 -0
- package/dist/index.js +26 -6
- package/docs/API.md +4 -1
- package/docs/RECIPES.md +10 -1
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -94,6 +94,17 @@ const chart = createChart(root, {
|
|
|
94
94
|
});
|
|
95
95
|
```
|
|
96
96
|
|
|
97
|
+
## Axis Label Density
|
|
98
|
+
|
|
99
|
+
```ts
|
|
100
|
+
const chart = createChart(root, {
|
|
101
|
+
grid: {
|
|
102
|
+
xTickCount: 8, // bottom labels/grid intervals
|
|
103
|
+
yTickCount: 6 // right-side price labels/grid intervals
|
|
104
|
+
}
|
|
105
|
+
});
|
|
106
|
+
```
|
|
107
|
+
|
|
97
108
|
## Crosshair "+" Button
|
|
98
109
|
|
|
99
110
|
```ts
|
|
@@ -142,6 +153,7 @@ Volume scaling tip (for large spikes):
|
|
|
142
153
|
chart.updateIndicator(volumeId, {
|
|
143
154
|
inputs: {
|
|
144
155
|
scaleMode: "visible", // default; scales using current viewport only
|
|
156
|
+
scaleType: "sqrt", // default; balanced compression
|
|
145
157
|
clampPercentile: 0.95 // optional outlier clamp
|
|
146
158
|
}
|
|
147
159
|
});
|
|
@@ -28,7 +28,9 @@ var DEFAULT_GRID_OPTIONS = {
|
|
|
28
28
|
opacity: 0.38,
|
|
29
29
|
horizontalLines: true,
|
|
30
30
|
verticalLines: true,
|
|
31
|
-
|
|
31
|
+
xTickCount: 8,
|
|
32
|
+
yTickCount: 6,
|
|
33
|
+
horizontalTickCount: 6
|
|
32
34
|
};
|
|
33
35
|
var DEFAULT_AXIS_OPTIONS = {
|
|
34
36
|
lineColor: "#3b3f47",
|
|
@@ -531,6 +533,7 @@ var BUILTIN_VOLUME_INDICATOR = {
|
|
|
531
533
|
minBarWidth: 1,
|
|
532
534
|
overlayHeightRatio: 0.22,
|
|
533
535
|
scaleMode: "visible",
|
|
536
|
+
scaleType: "sqrt",
|
|
534
537
|
clampPercentile: 1
|
|
535
538
|
},
|
|
536
539
|
draw: (ctx, renderContext, inputs) => {
|
|
@@ -541,6 +544,7 @@ var BUILTIN_VOLUME_INDICATOR = {
|
|
|
541
544
|
const paneHeight = overlayMode ? Math.max(20, Math.round(fullPaneHeight * overlayHeightRatio)) : fullPaneHeight;
|
|
542
545
|
const paneBottom = chartBottom;
|
|
543
546
|
const scaleMode = inputs.scaleMode === "full" ? "full" : "visible";
|
|
547
|
+
const scaleType = inputs.scaleType === "linear" ? "linear" : inputs.scaleType === "log" ? "log" : "sqrt";
|
|
544
548
|
const scalingPoints = scaleMode === "full" ? data : data.slice(startIndex, endIndex + 1);
|
|
545
549
|
const scalingVolumes = scalingPoints.map((point) => Math.max(0, point.v ?? 0)).filter((value) => value > 0);
|
|
546
550
|
const clampPercentile = Number.isFinite(inputs.clampPercentile) ? Number(inputs.clampPercentile) : 1;
|
|
@@ -556,7 +560,8 @@ var BUILTIN_VOLUME_INDICATOR = {
|
|
|
556
560
|
if (!point || point.v === void 0 || point.v <= 0) {
|
|
557
561
|
continue;
|
|
558
562
|
}
|
|
559
|
-
const
|
|
563
|
+
const clampedVolume = Math.min(maxVolume, Math.max(0, point.v));
|
|
564
|
+
const ratio = scaleType === "linear" ? Math.min(1, Math.max(0, clampedVolume / maxVolume)) : scaleType === "log" ? Math.min(1, Math.max(0, Math.log1p(clampedVolume) / Math.log1p(maxVolume))) : Math.min(1, Math.max(0, Math.sqrt(clampedVolume) / Math.sqrt(maxVolume)));
|
|
560
565
|
const volumeHeight = Math.max(1, Math.round(paneHeight * ratio));
|
|
561
566
|
const xCenter = xFromIndex(index);
|
|
562
567
|
const barX = Math.round(xCenter - barWidth / 2);
|
|
@@ -1553,7 +1558,7 @@ function createChart(element, options = {}) {
|
|
|
1553
1558
|
const fullChartBottom = chartTop + fullChartHeight;
|
|
1554
1559
|
const chartRight = chartLeft + chartWidth;
|
|
1555
1560
|
const watermark = { ...DEFAULT_WATERMARK_OPTIONS, ...mergedOptions.watermark ?? {} };
|
|
1556
|
-
const paneGap =
|
|
1561
|
+
const paneGap = 0;
|
|
1557
1562
|
const separatePaneSpacing = 6;
|
|
1558
1563
|
const activeSeparateIndicators = indicators.filter((indicator) => indicator.visible).map((indicator) => ({ indicator, plugin: indicatorRegistry.get(indicator.type) })).filter(
|
|
1559
1564
|
(value) => value.plugin !== void 0 && (value.indicator.pane ?? value.plugin.pane ?? "overlay") === "separate"
|
|
@@ -1748,7 +1753,8 @@ function createChart(element, options = {}) {
|
|
|
1748
1753
|
);
|
|
1749
1754
|
const grid = { ...DEFAULT_GRID_OPTIONS, ...mergedOptions.grid ?? {} };
|
|
1750
1755
|
const gridOpacity = clamp(grid.opacity, 0, 1);
|
|
1751
|
-
const
|
|
1756
|
+
const yTickCountInput = grid.yTickCount ?? grid.horizontalTickCount;
|
|
1757
|
+
const yTicks = Math.max(1, Math.floor(yTickCountInput));
|
|
1752
1758
|
const gridColor = grid.color ?? mergedOptions.gridColor;
|
|
1753
1759
|
if (grid.horizontalLines) {
|
|
1754
1760
|
ctx.save();
|
|
@@ -1767,8 +1773,10 @@ function createChart(element, options = {}) {
|
|
|
1767
1773
|
ctx.restore();
|
|
1768
1774
|
}
|
|
1769
1775
|
const minLabelSpacingPx = Math.max(72, candleSpacing * 6);
|
|
1770
|
-
const
|
|
1771
|
-
const
|
|
1776
|
+
const autoLabelCount = Math.max(2, Math.floor(chartWidth / minLabelSpacingPx));
|
|
1777
|
+
const xTickCountInput = grid.xTickCount ?? autoLabelCount;
|
|
1778
|
+
const xTickCount = Math.max(2, Math.floor(xTickCountInput));
|
|
1779
|
+
const rawStep = xSpan / xTickCount;
|
|
1772
1780
|
const xStep = Math.max(1, Math.ceil(rawStep));
|
|
1773
1781
|
const visibleTickStart = Math.floor(xStart);
|
|
1774
1782
|
const visibleTickEnd = Math.ceil(xEnd) - 1;
|
|
@@ -1909,6 +1917,18 @@ function createChart(element, options = {}) {
|
|
|
1909
1917
|
ctx.restore();
|
|
1910
1918
|
});
|
|
1911
1919
|
}
|
|
1920
|
+
if (crosshair.visible && crosshairPoint && crosshair.showVertical) {
|
|
1921
|
+
const cx = clamp(crosshairPoint.x, chartLeft, chartRight);
|
|
1922
|
+
ctx.save();
|
|
1923
|
+
ctx.strokeStyle = crosshair.color;
|
|
1924
|
+
ctx.lineWidth = Math.max(1, crosshair.width);
|
|
1925
|
+
applyDashPattern(crosshair.style, dashPatterns.dotted, dashPatterns.dashed);
|
|
1926
|
+
ctx.beginPath();
|
|
1927
|
+
ctx.moveTo(crisp(cx), crisp(chartTop));
|
|
1928
|
+
ctx.lineTo(crisp(cx), crisp(fullChartBottom));
|
|
1929
|
+
ctx.stroke();
|
|
1930
|
+
ctx.restore();
|
|
1931
|
+
}
|
|
1912
1932
|
ctx.strokeStyle = axis.lineColor;
|
|
1913
1933
|
ctx.lineWidth = Math.max(1, axis.lineWidth);
|
|
1914
1934
|
ctx.beginPath();
|
|
@@ -4,7 +4,9 @@ var DEFAULT_GRID_OPTIONS = {
|
|
|
4
4
|
opacity: 0.38,
|
|
5
5
|
horizontalLines: true,
|
|
6
6
|
verticalLines: true,
|
|
7
|
-
|
|
7
|
+
xTickCount: 8,
|
|
8
|
+
yTickCount: 6,
|
|
9
|
+
horizontalTickCount: 6
|
|
8
10
|
};
|
|
9
11
|
var DEFAULT_AXIS_OPTIONS = {
|
|
10
12
|
lineColor: "#3b3f47",
|
|
@@ -507,6 +509,7 @@ var BUILTIN_VOLUME_INDICATOR = {
|
|
|
507
509
|
minBarWidth: 1,
|
|
508
510
|
overlayHeightRatio: 0.22,
|
|
509
511
|
scaleMode: "visible",
|
|
512
|
+
scaleType: "sqrt",
|
|
510
513
|
clampPercentile: 1
|
|
511
514
|
},
|
|
512
515
|
draw: (ctx, renderContext, inputs) => {
|
|
@@ -517,6 +520,7 @@ var BUILTIN_VOLUME_INDICATOR = {
|
|
|
517
520
|
const paneHeight = overlayMode ? Math.max(20, Math.round(fullPaneHeight * overlayHeightRatio)) : fullPaneHeight;
|
|
518
521
|
const paneBottom = chartBottom;
|
|
519
522
|
const scaleMode = inputs.scaleMode === "full" ? "full" : "visible";
|
|
523
|
+
const scaleType = inputs.scaleType === "linear" ? "linear" : inputs.scaleType === "log" ? "log" : "sqrt";
|
|
520
524
|
const scalingPoints = scaleMode === "full" ? data : data.slice(startIndex, endIndex + 1);
|
|
521
525
|
const scalingVolumes = scalingPoints.map((point) => Math.max(0, point.v ?? 0)).filter((value) => value > 0);
|
|
522
526
|
const clampPercentile = Number.isFinite(inputs.clampPercentile) ? Number(inputs.clampPercentile) : 1;
|
|
@@ -532,7 +536,8 @@ var BUILTIN_VOLUME_INDICATOR = {
|
|
|
532
536
|
if (!point || point.v === void 0 || point.v <= 0) {
|
|
533
537
|
continue;
|
|
534
538
|
}
|
|
535
|
-
const
|
|
539
|
+
const clampedVolume = Math.min(maxVolume, Math.max(0, point.v));
|
|
540
|
+
const ratio = scaleType === "linear" ? Math.min(1, Math.max(0, clampedVolume / maxVolume)) : scaleType === "log" ? Math.min(1, Math.max(0, Math.log1p(clampedVolume) / Math.log1p(maxVolume))) : Math.min(1, Math.max(0, Math.sqrt(clampedVolume) / Math.sqrt(maxVolume)));
|
|
536
541
|
const volumeHeight = Math.max(1, Math.round(paneHeight * ratio));
|
|
537
542
|
const xCenter = xFromIndex(index);
|
|
538
543
|
const barX = Math.round(xCenter - barWidth / 2);
|
|
@@ -1529,7 +1534,7 @@ function createChart(element, options = {}) {
|
|
|
1529
1534
|
const fullChartBottom = chartTop + fullChartHeight;
|
|
1530
1535
|
const chartRight = chartLeft + chartWidth;
|
|
1531
1536
|
const watermark = { ...DEFAULT_WATERMARK_OPTIONS, ...mergedOptions.watermark ?? {} };
|
|
1532
|
-
const paneGap =
|
|
1537
|
+
const paneGap = 0;
|
|
1533
1538
|
const separatePaneSpacing = 6;
|
|
1534
1539
|
const activeSeparateIndicators = indicators.filter((indicator) => indicator.visible).map((indicator) => ({ indicator, plugin: indicatorRegistry.get(indicator.type) })).filter(
|
|
1535
1540
|
(value) => value.plugin !== void 0 && (value.indicator.pane ?? value.plugin.pane ?? "overlay") === "separate"
|
|
@@ -1724,7 +1729,8 @@ function createChart(element, options = {}) {
|
|
|
1724
1729
|
);
|
|
1725
1730
|
const grid = { ...DEFAULT_GRID_OPTIONS, ...mergedOptions.grid ?? {} };
|
|
1726
1731
|
const gridOpacity = clamp(grid.opacity, 0, 1);
|
|
1727
|
-
const
|
|
1732
|
+
const yTickCountInput = grid.yTickCount ?? grid.horizontalTickCount;
|
|
1733
|
+
const yTicks = Math.max(1, Math.floor(yTickCountInput));
|
|
1728
1734
|
const gridColor = grid.color ?? mergedOptions.gridColor;
|
|
1729
1735
|
if (grid.horizontalLines) {
|
|
1730
1736
|
ctx.save();
|
|
@@ -1743,8 +1749,10 @@ function createChart(element, options = {}) {
|
|
|
1743
1749
|
ctx.restore();
|
|
1744
1750
|
}
|
|
1745
1751
|
const minLabelSpacingPx = Math.max(72, candleSpacing * 6);
|
|
1746
|
-
const
|
|
1747
|
-
const
|
|
1752
|
+
const autoLabelCount = Math.max(2, Math.floor(chartWidth / minLabelSpacingPx));
|
|
1753
|
+
const xTickCountInput = grid.xTickCount ?? autoLabelCount;
|
|
1754
|
+
const xTickCount = Math.max(2, Math.floor(xTickCountInput));
|
|
1755
|
+
const rawStep = xSpan / xTickCount;
|
|
1748
1756
|
const xStep = Math.max(1, Math.ceil(rawStep));
|
|
1749
1757
|
const visibleTickStart = Math.floor(xStart);
|
|
1750
1758
|
const visibleTickEnd = Math.ceil(xEnd) - 1;
|
|
@@ -1885,6 +1893,18 @@ function createChart(element, options = {}) {
|
|
|
1885
1893
|
ctx.restore();
|
|
1886
1894
|
});
|
|
1887
1895
|
}
|
|
1896
|
+
if (crosshair.visible && crosshairPoint && crosshair.showVertical) {
|
|
1897
|
+
const cx = clamp(crosshairPoint.x, chartLeft, chartRight);
|
|
1898
|
+
ctx.save();
|
|
1899
|
+
ctx.strokeStyle = crosshair.color;
|
|
1900
|
+
ctx.lineWidth = Math.max(1, crosshair.width);
|
|
1901
|
+
applyDashPattern(crosshair.style, dashPatterns.dotted, dashPatterns.dashed);
|
|
1902
|
+
ctx.beginPath();
|
|
1903
|
+
ctx.moveTo(crisp(cx), crisp(chartTop));
|
|
1904
|
+
ctx.lineTo(crisp(cx), crisp(fullChartBottom));
|
|
1905
|
+
ctx.stroke();
|
|
1906
|
+
ctx.restore();
|
|
1907
|
+
}
|
|
1888
1908
|
ctx.strokeStyle = axis.lineColor;
|
|
1889
1909
|
ctx.lineWidth = Math.max(1, axis.lineWidth);
|
|
1890
1910
|
ctx.beginPath();
|
package/dist/index.cjs
CHANGED
|
@@ -28,7 +28,9 @@ var DEFAULT_GRID_OPTIONS = {
|
|
|
28
28
|
opacity: 0.38,
|
|
29
29
|
horizontalLines: true,
|
|
30
30
|
verticalLines: true,
|
|
31
|
-
|
|
31
|
+
xTickCount: 8,
|
|
32
|
+
yTickCount: 6,
|
|
33
|
+
horizontalTickCount: 6
|
|
32
34
|
};
|
|
33
35
|
var DEFAULT_AXIS_OPTIONS = {
|
|
34
36
|
lineColor: "#3b3f47",
|
|
@@ -531,6 +533,7 @@ var BUILTIN_VOLUME_INDICATOR = {
|
|
|
531
533
|
minBarWidth: 1,
|
|
532
534
|
overlayHeightRatio: 0.22,
|
|
533
535
|
scaleMode: "visible",
|
|
536
|
+
scaleType: "sqrt",
|
|
534
537
|
clampPercentile: 1
|
|
535
538
|
},
|
|
536
539
|
draw: (ctx, renderContext, inputs) => {
|
|
@@ -541,6 +544,7 @@ var BUILTIN_VOLUME_INDICATOR = {
|
|
|
541
544
|
const paneHeight = overlayMode ? Math.max(20, Math.round(fullPaneHeight * overlayHeightRatio)) : fullPaneHeight;
|
|
542
545
|
const paneBottom = chartBottom;
|
|
543
546
|
const scaleMode = inputs.scaleMode === "full" ? "full" : "visible";
|
|
547
|
+
const scaleType = inputs.scaleType === "linear" ? "linear" : inputs.scaleType === "log" ? "log" : "sqrt";
|
|
544
548
|
const scalingPoints = scaleMode === "full" ? data : data.slice(startIndex, endIndex + 1);
|
|
545
549
|
const scalingVolumes = scalingPoints.map((point) => Math.max(0, point.v ?? 0)).filter((value) => value > 0);
|
|
546
550
|
const clampPercentile = Number.isFinite(inputs.clampPercentile) ? Number(inputs.clampPercentile) : 1;
|
|
@@ -556,7 +560,8 @@ var BUILTIN_VOLUME_INDICATOR = {
|
|
|
556
560
|
if (!point || point.v === void 0 || point.v <= 0) {
|
|
557
561
|
continue;
|
|
558
562
|
}
|
|
559
|
-
const
|
|
563
|
+
const clampedVolume = Math.min(maxVolume, Math.max(0, point.v));
|
|
564
|
+
const ratio = scaleType === "linear" ? Math.min(1, Math.max(0, clampedVolume / maxVolume)) : scaleType === "log" ? Math.min(1, Math.max(0, Math.log1p(clampedVolume) / Math.log1p(maxVolume))) : Math.min(1, Math.max(0, Math.sqrt(clampedVolume) / Math.sqrt(maxVolume)));
|
|
560
565
|
const volumeHeight = Math.max(1, Math.round(paneHeight * ratio));
|
|
561
566
|
const xCenter = xFromIndex(index);
|
|
562
567
|
const barX = Math.round(xCenter - barWidth / 2);
|
|
@@ -1553,7 +1558,7 @@ function createChart(element, options = {}) {
|
|
|
1553
1558
|
const fullChartBottom = chartTop + fullChartHeight;
|
|
1554
1559
|
const chartRight = chartLeft + chartWidth;
|
|
1555
1560
|
const watermark = { ...DEFAULT_WATERMARK_OPTIONS, ...mergedOptions.watermark ?? {} };
|
|
1556
|
-
const paneGap =
|
|
1561
|
+
const paneGap = 0;
|
|
1557
1562
|
const separatePaneSpacing = 6;
|
|
1558
1563
|
const activeSeparateIndicators = indicators.filter((indicator) => indicator.visible).map((indicator) => ({ indicator, plugin: indicatorRegistry.get(indicator.type) })).filter(
|
|
1559
1564
|
(value) => value.plugin !== void 0 && (value.indicator.pane ?? value.plugin.pane ?? "overlay") === "separate"
|
|
@@ -1748,7 +1753,8 @@ function createChart(element, options = {}) {
|
|
|
1748
1753
|
);
|
|
1749
1754
|
const grid = { ...DEFAULT_GRID_OPTIONS, ...mergedOptions.grid ?? {} };
|
|
1750
1755
|
const gridOpacity = clamp(grid.opacity, 0, 1);
|
|
1751
|
-
const
|
|
1756
|
+
const yTickCountInput = grid.yTickCount ?? grid.horizontalTickCount;
|
|
1757
|
+
const yTicks = Math.max(1, Math.floor(yTickCountInput));
|
|
1752
1758
|
const gridColor = grid.color ?? mergedOptions.gridColor;
|
|
1753
1759
|
if (grid.horizontalLines) {
|
|
1754
1760
|
ctx.save();
|
|
@@ -1767,8 +1773,10 @@ function createChart(element, options = {}) {
|
|
|
1767
1773
|
ctx.restore();
|
|
1768
1774
|
}
|
|
1769
1775
|
const minLabelSpacingPx = Math.max(72, candleSpacing * 6);
|
|
1770
|
-
const
|
|
1771
|
-
const
|
|
1776
|
+
const autoLabelCount = Math.max(2, Math.floor(chartWidth / minLabelSpacingPx));
|
|
1777
|
+
const xTickCountInput = grid.xTickCount ?? autoLabelCount;
|
|
1778
|
+
const xTickCount = Math.max(2, Math.floor(xTickCountInput));
|
|
1779
|
+
const rawStep = xSpan / xTickCount;
|
|
1772
1780
|
const xStep = Math.max(1, Math.ceil(rawStep));
|
|
1773
1781
|
const visibleTickStart = Math.floor(xStart);
|
|
1774
1782
|
const visibleTickEnd = Math.ceil(xEnd) - 1;
|
|
@@ -1909,6 +1917,18 @@ function createChart(element, options = {}) {
|
|
|
1909
1917
|
ctx.restore();
|
|
1910
1918
|
});
|
|
1911
1919
|
}
|
|
1920
|
+
if (crosshair.visible && crosshairPoint && crosshair.showVertical) {
|
|
1921
|
+
const cx = clamp(crosshairPoint.x, chartLeft, chartRight);
|
|
1922
|
+
ctx.save();
|
|
1923
|
+
ctx.strokeStyle = crosshair.color;
|
|
1924
|
+
ctx.lineWidth = Math.max(1, crosshair.width);
|
|
1925
|
+
applyDashPattern(crosshair.style, dashPatterns.dotted, dashPatterns.dashed);
|
|
1926
|
+
ctx.beginPath();
|
|
1927
|
+
ctx.moveTo(crisp(cx), crisp(chartTop));
|
|
1928
|
+
ctx.lineTo(crisp(cx), crisp(fullChartBottom));
|
|
1929
|
+
ctx.stroke();
|
|
1930
|
+
ctx.restore();
|
|
1931
|
+
}
|
|
1912
1932
|
ctx.strokeStyle = axis.lineColor;
|
|
1913
1933
|
ctx.lineWidth = Math.max(1, axis.lineWidth);
|
|
1914
1934
|
ctx.beginPath();
|
package/dist/index.d.cts
CHANGED
package/dist/index.d.ts
CHANGED
package/dist/index.js
CHANGED
|
@@ -4,7 +4,9 @@ var DEFAULT_GRID_OPTIONS = {
|
|
|
4
4
|
opacity: 0.38,
|
|
5
5
|
horizontalLines: true,
|
|
6
6
|
verticalLines: true,
|
|
7
|
-
|
|
7
|
+
xTickCount: 8,
|
|
8
|
+
yTickCount: 6,
|
|
9
|
+
horizontalTickCount: 6
|
|
8
10
|
};
|
|
9
11
|
var DEFAULT_AXIS_OPTIONS = {
|
|
10
12
|
lineColor: "#3b3f47",
|
|
@@ -507,6 +509,7 @@ var BUILTIN_VOLUME_INDICATOR = {
|
|
|
507
509
|
minBarWidth: 1,
|
|
508
510
|
overlayHeightRatio: 0.22,
|
|
509
511
|
scaleMode: "visible",
|
|
512
|
+
scaleType: "sqrt",
|
|
510
513
|
clampPercentile: 1
|
|
511
514
|
},
|
|
512
515
|
draw: (ctx, renderContext, inputs) => {
|
|
@@ -517,6 +520,7 @@ var BUILTIN_VOLUME_INDICATOR = {
|
|
|
517
520
|
const paneHeight = overlayMode ? Math.max(20, Math.round(fullPaneHeight * overlayHeightRatio)) : fullPaneHeight;
|
|
518
521
|
const paneBottom = chartBottom;
|
|
519
522
|
const scaleMode = inputs.scaleMode === "full" ? "full" : "visible";
|
|
523
|
+
const scaleType = inputs.scaleType === "linear" ? "linear" : inputs.scaleType === "log" ? "log" : "sqrt";
|
|
520
524
|
const scalingPoints = scaleMode === "full" ? data : data.slice(startIndex, endIndex + 1);
|
|
521
525
|
const scalingVolumes = scalingPoints.map((point) => Math.max(0, point.v ?? 0)).filter((value) => value > 0);
|
|
522
526
|
const clampPercentile = Number.isFinite(inputs.clampPercentile) ? Number(inputs.clampPercentile) : 1;
|
|
@@ -532,7 +536,8 @@ var BUILTIN_VOLUME_INDICATOR = {
|
|
|
532
536
|
if (!point || point.v === void 0 || point.v <= 0) {
|
|
533
537
|
continue;
|
|
534
538
|
}
|
|
535
|
-
const
|
|
539
|
+
const clampedVolume = Math.min(maxVolume, Math.max(0, point.v));
|
|
540
|
+
const ratio = scaleType === "linear" ? Math.min(1, Math.max(0, clampedVolume / maxVolume)) : scaleType === "log" ? Math.min(1, Math.max(0, Math.log1p(clampedVolume) / Math.log1p(maxVolume))) : Math.min(1, Math.max(0, Math.sqrt(clampedVolume) / Math.sqrt(maxVolume)));
|
|
536
541
|
const volumeHeight = Math.max(1, Math.round(paneHeight * ratio));
|
|
537
542
|
const xCenter = xFromIndex(index);
|
|
538
543
|
const barX = Math.round(xCenter - barWidth / 2);
|
|
@@ -1529,7 +1534,7 @@ function createChart(element, options = {}) {
|
|
|
1529
1534
|
const fullChartBottom = chartTop + fullChartHeight;
|
|
1530
1535
|
const chartRight = chartLeft + chartWidth;
|
|
1531
1536
|
const watermark = { ...DEFAULT_WATERMARK_OPTIONS, ...mergedOptions.watermark ?? {} };
|
|
1532
|
-
const paneGap =
|
|
1537
|
+
const paneGap = 0;
|
|
1533
1538
|
const separatePaneSpacing = 6;
|
|
1534
1539
|
const activeSeparateIndicators = indicators.filter((indicator) => indicator.visible).map((indicator) => ({ indicator, plugin: indicatorRegistry.get(indicator.type) })).filter(
|
|
1535
1540
|
(value) => value.plugin !== void 0 && (value.indicator.pane ?? value.plugin.pane ?? "overlay") === "separate"
|
|
@@ -1724,7 +1729,8 @@ function createChart(element, options = {}) {
|
|
|
1724
1729
|
);
|
|
1725
1730
|
const grid = { ...DEFAULT_GRID_OPTIONS, ...mergedOptions.grid ?? {} };
|
|
1726
1731
|
const gridOpacity = clamp(grid.opacity, 0, 1);
|
|
1727
|
-
const
|
|
1732
|
+
const yTickCountInput = grid.yTickCount ?? grid.horizontalTickCount;
|
|
1733
|
+
const yTicks = Math.max(1, Math.floor(yTickCountInput));
|
|
1728
1734
|
const gridColor = grid.color ?? mergedOptions.gridColor;
|
|
1729
1735
|
if (grid.horizontalLines) {
|
|
1730
1736
|
ctx.save();
|
|
@@ -1743,8 +1749,10 @@ function createChart(element, options = {}) {
|
|
|
1743
1749
|
ctx.restore();
|
|
1744
1750
|
}
|
|
1745
1751
|
const minLabelSpacingPx = Math.max(72, candleSpacing * 6);
|
|
1746
|
-
const
|
|
1747
|
-
const
|
|
1752
|
+
const autoLabelCount = Math.max(2, Math.floor(chartWidth / minLabelSpacingPx));
|
|
1753
|
+
const xTickCountInput = grid.xTickCount ?? autoLabelCount;
|
|
1754
|
+
const xTickCount = Math.max(2, Math.floor(xTickCountInput));
|
|
1755
|
+
const rawStep = xSpan / xTickCount;
|
|
1748
1756
|
const xStep = Math.max(1, Math.ceil(rawStep));
|
|
1749
1757
|
const visibleTickStart = Math.floor(xStart);
|
|
1750
1758
|
const visibleTickEnd = Math.ceil(xEnd) - 1;
|
|
@@ -1885,6 +1893,18 @@ function createChart(element, options = {}) {
|
|
|
1885
1893
|
ctx.restore();
|
|
1886
1894
|
});
|
|
1887
1895
|
}
|
|
1896
|
+
if (crosshair.visible && crosshairPoint && crosshair.showVertical) {
|
|
1897
|
+
const cx = clamp(crosshairPoint.x, chartLeft, chartRight);
|
|
1898
|
+
ctx.save();
|
|
1899
|
+
ctx.strokeStyle = crosshair.color;
|
|
1900
|
+
ctx.lineWidth = Math.max(1, crosshair.width);
|
|
1901
|
+
applyDashPattern(crosshair.style, dashPatterns.dotted, dashPatterns.dashed);
|
|
1902
|
+
ctx.beginPath();
|
|
1903
|
+
ctx.moveTo(crisp(cx), crisp(chartTop));
|
|
1904
|
+
ctx.lineTo(crisp(cx), crisp(fullChartBottom));
|
|
1905
|
+
ctx.stroke();
|
|
1906
|
+
ctx.restore();
|
|
1907
|
+
}
|
|
1888
1908
|
ctx.strokeStyle = axis.lineColor;
|
|
1889
1909
|
ctx.lineWidth = Math.max(1, axis.lineWidth);
|
|
1890
1910
|
ctx.beginPath();
|
package/docs/API.md
CHANGED
|
@@ -81,7 +81,9 @@ Top-level options:
|
|
|
81
81
|
- `opacity` (default `0.38`)
|
|
82
82
|
- `horizontalLines` (default `true`)
|
|
83
83
|
- `verticalLines` (default `true`)
|
|
84
|
-
- `
|
|
84
|
+
- `xTickCount` (default `8`, x-axis label/grid density)
|
|
85
|
+
- `yTickCount` (default `6`, y-axis label/grid density)
|
|
86
|
+
- `horizontalTickCount` (legacy alias for `yTickCount`)
|
|
85
87
|
|
|
86
88
|
### `CrosshairOptions`
|
|
87
89
|
|
|
@@ -310,6 +312,7 @@ Volume style inputs:
|
|
|
310
312
|
- `minBarWidth`
|
|
311
313
|
- `overlayHeightRatio` (when used as overlay)
|
|
312
314
|
- `scaleMode` (`"visible"` default, or `"full"`)
|
|
315
|
+
- `scaleType` (`"sqrt"` default, or `"log"` / `"linear"`)
|
|
313
316
|
- `clampPercentile` (`0..1`, default `1`; e.g. `0.95` to reduce outlier crush)
|
|
314
317
|
|
|
315
318
|
---
|
package/docs/RECIPES.md
CHANGED
|
@@ -11,7 +11,15 @@ const chart = createChart(rootEl, {
|
|
|
11
11
|
backgroundColor: "#101114",
|
|
12
12
|
upColor: "#2fb171",
|
|
13
13
|
downColor: "#d35a5a",
|
|
14
|
-
grid: { opacity: 0.35,
|
|
14
|
+
grid: { opacity: 0.35, xTickCount: 8, yTickCount: 6 }
|
|
15
|
+
});
|
|
16
|
+
```
|
|
17
|
+
|
|
18
|
+
Tune axis density when creating the chart:
|
|
19
|
+
|
|
20
|
+
```ts
|
|
21
|
+
const chartDense = createChart(rootEl, {
|
|
22
|
+
grid: { xTickCount: 10, yTickCount: 8 }
|
|
15
23
|
});
|
|
16
24
|
```
|
|
17
25
|
|
|
@@ -143,6 +151,7 @@ const volumeId = chart.addIndicator("volume", { upOpacity: 0.72, downOpacity: 0.
|
|
|
143
151
|
```ts
|
|
144
152
|
const volumeId = chart.addIndicator("volume", {
|
|
145
153
|
scaleMode: "visible", // default behavior
|
|
154
|
+
scaleType: "sqrt", // default behavior
|
|
146
155
|
clampPercentile: 0.95 // clamp scaling reference to p95 of visible bars
|
|
147
156
|
});
|
|
148
157
|
```
|