hyperprop-charting-library 0.1.25 → 0.1.26

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 CHANGED
@@ -136,6 +136,17 @@ chart.updateIndicator(emaId, { inputs: { length: 55 } });
136
136
  chart.updateIndicator(volumeId, { paneHeightRatio: 0.1 });
137
137
  ```
138
138
 
139
+ Volume scaling tip (for large spikes):
140
+
141
+ ```ts
142
+ chart.updateIndicator(volumeId, {
143
+ inputs: {
144
+ scaleMode: "visible", // default; scales using current viewport only
145
+ clampPercentile: 0.95 // optional outlier clamp
146
+ }
147
+ });
148
+ ```
149
+
139
150
  Autoscale controls per indicator:
140
151
 
141
152
  ```ts
@@ -509,6 +509,15 @@ var drawSeparateSeries = (ctx, renderContext, values, color, width, minOverride,
509
509
  }
510
510
  ctx.restore();
511
511
  };
512
+ var getPercentileValue = (values, percentile) => {
513
+ if (values.length === 0) {
514
+ return 1;
515
+ }
516
+ const sorted = [...values].sort((a, b) => a - b);
517
+ const clamped = Math.min(1, Math.max(0, percentile));
518
+ const index = Math.min(sorted.length - 1, Math.max(0, Math.ceil(clamped * sorted.length) - 1));
519
+ return sorted[index] ?? 1;
520
+ };
512
521
  var BUILTIN_VOLUME_INDICATOR = {
513
522
  id: "volume",
514
523
  name: "Volume",
@@ -520,7 +529,9 @@ var BUILTIN_VOLUME_INDICATOR = {
520
529
  upColor: "",
521
530
  downColor: "",
522
531
  minBarWidth: 1,
523
- overlayHeightRatio: 0.22
532
+ overlayHeightRatio: 0.22,
533
+ scaleMode: "visible",
534
+ clampPercentile: 1
524
535
  },
525
536
  draw: (ctx, renderContext, inputs) => {
526
537
  const { data, startIndex, endIndex, chartTop, chartBottom, candleSpacing, xFromIndex, upColor, downColor } = renderContext;
@@ -529,8 +540,11 @@ var BUILTIN_VOLUME_INDICATOR = {
529
540
  const overlayHeightRatio = Math.min(0.45, Math.max(0.08, Number(inputs.overlayHeightRatio) || 0.22));
530
541
  const paneHeight = overlayMode ? Math.max(20, Math.round(fullPaneHeight * overlayHeightRatio)) : fullPaneHeight;
531
542
  const paneBottom = chartBottom;
532
- const visiblePoints = data.slice(startIndex, endIndex + 1);
533
- const maxVolume = Math.max(1, ...visiblePoints.map((point) => point.v ?? 0));
543
+ const scaleMode = inputs.scaleMode === "full" ? "full" : "visible";
544
+ const scalingPoints = scaleMode === "full" ? data : data.slice(startIndex, endIndex + 1);
545
+ const scalingVolumes = scalingPoints.map((point) => Math.max(0, point.v ?? 0)).filter((value) => value > 0);
546
+ const clampPercentile = Number.isFinite(inputs.clampPercentile) ? Number(inputs.clampPercentile) : 1;
547
+ const maxVolume = Math.max(1, getPercentileValue(scalingVolumes, clampPercentile));
534
548
  const barWidth = Math.max(
535
549
  Math.max(1, Number(inputs.minBarWidth) || 1),
536
550
  Math.min(Math.max(1, candleSpacing - 1), Math.floor(candleSpacing * 0.7))
@@ -485,6 +485,15 @@ var drawSeparateSeries = (ctx, renderContext, values, color, width, minOverride,
485
485
  }
486
486
  ctx.restore();
487
487
  };
488
+ var getPercentileValue = (values, percentile) => {
489
+ if (values.length === 0) {
490
+ return 1;
491
+ }
492
+ const sorted = [...values].sort((a, b) => a - b);
493
+ const clamped = Math.min(1, Math.max(0, percentile));
494
+ const index = Math.min(sorted.length - 1, Math.max(0, Math.ceil(clamped * sorted.length) - 1));
495
+ return sorted[index] ?? 1;
496
+ };
488
497
  var BUILTIN_VOLUME_INDICATOR = {
489
498
  id: "volume",
490
499
  name: "Volume",
@@ -496,7 +505,9 @@ var BUILTIN_VOLUME_INDICATOR = {
496
505
  upColor: "",
497
506
  downColor: "",
498
507
  minBarWidth: 1,
499
- overlayHeightRatio: 0.22
508
+ overlayHeightRatio: 0.22,
509
+ scaleMode: "visible",
510
+ clampPercentile: 1
500
511
  },
501
512
  draw: (ctx, renderContext, inputs) => {
502
513
  const { data, startIndex, endIndex, chartTop, chartBottom, candleSpacing, xFromIndex, upColor, downColor } = renderContext;
@@ -505,8 +516,11 @@ var BUILTIN_VOLUME_INDICATOR = {
505
516
  const overlayHeightRatio = Math.min(0.45, Math.max(0.08, Number(inputs.overlayHeightRatio) || 0.22));
506
517
  const paneHeight = overlayMode ? Math.max(20, Math.round(fullPaneHeight * overlayHeightRatio)) : fullPaneHeight;
507
518
  const paneBottom = chartBottom;
508
- const visiblePoints = data.slice(startIndex, endIndex + 1);
509
- const maxVolume = Math.max(1, ...visiblePoints.map((point) => point.v ?? 0));
519
+ const scaleMode = inputs.scaleMode === "full" ? "full" : "visible";
520
+ const scalingPoints = scaleMode === "full" ? data : data.slice(startIndex, endIndex + 1);
521
+ const scalingVolumes = scalingPoints.map((point) => Math.max(0, point.v ?? 0)).filter((value) => value > 0);
522
+ const clampPercentile = Number.isFinite(inputs.clampPercentile) ? Number(inputs.clampPercentile) : 1;
523
+ const maxVolume = Math.max(1, getPercentileValue(scalingVolumes, clampPercentile));
510
524
  const barWidth = Math.max(
511
525
  Math.max(1, Number(inputs.minBarWidth) || 1),
512
526
  Math.min(Math.max(1, candleSpacing - 1), Math.floor(candleSpacing * 0.7))
package/dist/index.cjs CHANGED
@@ -509,6 +509,15 @@ var drawSeparateSeries = (ctx, renderContext, values, color, width, minOverride,
509
509
  }
510
510
  ctx.restore();
511
511
  };
512
+ var getPercentileValue = (values, percentile) => {
513
+ if (values.length === 0) {
514
+ return 1;
515
+ }
516
+ const sorted = [...values].sort((a, b) => a - b);
517
+ const clamped = Math.min(1, Math.max(0, percentile));
518
+ const index = Math.min(sorted.length - 1, Math.max(0, Math.ceil(clamped * sorted.length) - 1));
519
+ return sorted[index] ?? 1;
520
+ };
512
521
  var BUILTIN_VOLUME_INDICATOR = {
513
522
  id: "volume",
514
523
  name: "Volume",
@@ -520,7 +529,9 @@ var BUILTIN_VOLUME_INDICATOR = {
520
529
  upColor: "",
521
530
  downColor: "",
522
531
  minBarWidth: 1,
523
- overlayHeightRatio: 0.22
532
+ overlayHeightRatio: 0.22,
533
+ scaleMode: "visible",
534
+ clampPercentile: 1
524
535
  },
525
536
  draw: (ctx, renderContext, inputs) => {
526
537
  const { data, startIndex, endIndex, chartTop, chartBottom, candleSpacing, xFromIndex, upColor, downColor } = renderContext;
@@ -529,8 +540,11 @@ var BUILTIN_VOLUME_INDICATOR = {
529
540
  const overlayHeightRatio = Math.min(0.45, Math.max(0.08, Number(inputs.overlayHeightRatio) || 0.22));
530
541
  const paneHeight = overlayMode ? Math.max(20, Math.round(fullPaneHeight * overlayHeightRatio)) : fullPaneHeight;
531
542
  const paneBottom = chartBottom;
532
- const visiblePoints = data.slice(startIndex, endIndex + 1);
533
- const maxVolume = Math.max(1, ...visiblePoints.map((point) => point.v ?? 0));
543
+ const scaleMode = inputs.scaleMode === "full" ? "full" : "visible";
544
+ const scalingPoints = scaleMode === "full" ? data : data.slice(startIndex, endIndex + 1);
545
+ const scalingVolumes = scalingPoints.map((point) => Math.max(0, point.v ?? 0)).filter((value) => value > 0);
546
+ const clampPercentile = Number.isFinite(inputs.clampPercentile) ? Number(inputs.clampPercentile) : 1;
547
+ const maxVolume = Math.max(1, getPercentileValue(scalingVolumes, clampPercentile));
534
548
  const barWidth = Math.max(
535
549
  Math.max(1, Number(inputs.minBarWidth) || 1),
536
550
  Math.min(Math.max(1, candleSpacing - 1), Math.floor(candleSpacing * 0.7))
package/dist/index.js CHANGED
@@ -485,6 +485,15 @@ var drawSeparateSeries = (ctx, renderContext, values, color, width, minOverride,
485
485
  }
486
486
  ctx.restore();
487
487
  };
488
+ var getPercentileValue = (values, percentile) => {
489
+ if (values.length === 0) {
490
+ return 1;
491
+ }
492
+ const sorted = [...values].sort((a, b) => a - b);
493
+ const clamped = Math.min(1, Math.max(0, percentile));
494
+ const index = Math.min(sorted.length - 1, Math.max(0, Math.ceil(clamped * sorted.length) - 1));
495
+ return sorted[index] ?? 1;
496
+ };
488
497
  var BUILTIN_VOLUME_INDICATOR = {
489
498
  id: "volume",
490
499
  name: "Volume",
@@ -496,7 +505,9 @@ var BUILTIN_VOLUME_INDICATOR = {
496
505
  upColor: "",
497
506
  downColor: "",
498
507
  minBarWidth: 1,
499
- overlayHeightRatio: 0.22
508
+ overlayHeightRatio: 0.22,
509
+ scaleMode: "visible",
510
+ clampPercentile: 1
500
511
  },
501
512
  draw: (ctx, renderContext, inputs) => {
502
513
  const { data, startIndex, endIndex, chartTop, chartBottom, candleSpacing, xFromIndex, upColor, downColor } = renderContext;
@@ -505,8 +516,11 @@ var BUILTIN_VOLUME_INDICATOR = {
505
516
  const overlayHeightRatio = Math.min(0.45, Math.max(0.08, Number(inputs.overlayHeightRatio) || 0.22));
506
517
  const paneHeight = overlayMode ? Math.max(20, Math.round(fullPaneHeight * overlayHeightRatio)) : fullPaneHeight;
507
518
  const paneBottom = chartBottom;
508
- const visiblePoints = data.slice(startIndex, endIndex + 1);
509
- const maxVolume = Math.max(1, ...visiblePoints.map((point) => point.v ?? 0));
519
+ const scaleMode = inputs.scaleMode === "full" ? "full" : "visible";
520
+ const scalingPoints = scaleMode === "full" ? data : data.slice(startIndex, endIndex + 1);
521
+ const scalingVolumes = scalingPoints.map((point) => Math.max(0, point.v ?? 0)).filter((value) => value > 0);
522
+ const clampPercentile = Number.isFinite(inputs.clampPercentile) ? Number(inputs.clampPercentile) : 1;
523
+ const maxVolume = Math.max(1, getPercentileValue(scalingVolumes, clampPercentile));
510
524
  const barWidth = Math.max(
511
525
  Math.max(1, Number(inputs.minBarWidth) || 1),
512
526
  Math.min(Math.max(1, candleSpacing - 1), Math.floor(candleSpacing * 0.7))
package/docs/API.md CHANGED
@@ -309,6 +309,8 @@ Volume style inputs:
309
309
  - `upColor`, `downColor`
310
310
  - `minBarWidth`
311
311
  - `overlayHeightRatio` (when used as overlay)
312
+ - `scaleMode` (`"visible"` default, or `"full"`)
313
+ - `clampPercentile` (`0..1`, default `1`; e.g. `0.95` to reduce outlier crush)
312
314
 
313
315
  ---
314
316
 
package/docs/RECIPES.md CHANGED
@@ -138,6 +138,15 @@ const rsiId = chart.addIndicator("rsi", { length: 14 }, { pane: "separate", pane
138
138
  const volumeId = chart.addIndicator("volume", { upOpacity: 0.72, downOpacity: 0.72 });
139
139
  ```
140
140
 
141
+ ## Prevent one volume spike from crushing all bars
142
+
143
+ ```ts
144
+ const volumeId = chart.addIndicator("volume", {
145
+ scaleMode: "visible", // default behavior
146
+ clampPercentile: 0.95 // clamp scaling reference to p95 of visible bars
147
+ });
148
+ ```
149
+
141
150
  Available built-ins:
142
151
 
143
152
  - `volume`, `sma`, `ema`, `rsi`, `wma`, `vwma`, `rma`, `hma`, `stddev`, `atr`
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "hyperprop-charting-library",
3
- "version": "0.1.25",
3
+ "version": "0.1.26",
4
4
  "description": "Lightweight TypeScript charting core",
5
5
  "type": "module",
6
6
  "main": "./dist/hyperprop-charting-library.cjs",