hyperprop-charting-library 0.1.23 → 0.1.25
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 +44 -0
- package/dist/hyperprop-charting-library.cjs +291 -36
- package/dist/hyperprop-charting-library.d.ts +18 -1
- package/dist/hyperprop-charting-library.js +291 -36
- package/dist/index.cjs +291 -36
- package/dist/index.d.cts +18 -1
- package/dist/index.d.ts +18 -1
- package/dist/index.js +291 -36
- package/docs/API.md +18 -1
- package/docs/RECIPES.md +59 -0
- package/package.json +1 -1
package/dist/index.cjs
CHANGED
|
@@ -133,6 +133,8 @@ var DEFAULT_OPTIONS = {
|
|
|
133
133
|
backgroundColor: "#101114",
|
|
134
134
|
axisColor: "#7f8289",
|
|
135
135
|
axis: DEFAULT_AXIS_OPTIONS,
|
|
136
|
+
xAxis: DEFAULT_AXIS_OPTIONS,
|
|
137
|
+
yAxis: DEFAULT_AXIS_OPTIONS,
|
|
136
138
|
priceDecimals: 2,
|
|
137
139
|
stabilizePriceLabels: true,
|
|
138
140
|
priceLabelMinIntegerDigits: 3,
|
|
@@ -151,6 +153,9 @@ var DEFAULT_OPTIONS = {
|
|
|
151
153
|
candleBodyWidthRatio: 0.7,
|
|
152
154
|
candleMinWidth: 0.5,
|
|
153
155
|
candleWickWidth: 1,
|
|
156
|
+
tickSize: 0,
|
|
157
|
+
candleColorMode: "openClose",
|
|
158
|
+
candleColorEpsilon: -1,
|
|
154
159
|
autoScaleSmoothing: 0.16,
|
|
155
160
|
autoScaleIgnoreLatestCandle: true,
|
|
156
161
|
doubleClickEnabled: true,
|
|
@@ -386,6 +391,35 @@ var computeAtrSeries = (data, length) => {
|
|
|
386
391
|
}
|
|
387
392
|
return result;
|
|
388
393
|
};
|
|
394
|
+
var builtInSeriesCache = /* @__PURE__ */ new Map();
|
|
395
|
+
var getSeriesFingerprint = (data) => {
|
|
396
|
+
const length = data.length;
|
|
397
|
+
const last = length > 0 ? data[length - 1] : void 0;
|
|
398
|
+
const prev = length > 1 ? data[length - 2] : void 0;
|
|
399
|
+
if (!last) return "empty";
|
|
400
|
+
return [
|
|
401
|
+
length,
|
|
402
|
+
last.time.getTime(),
|
|
403
|
+
last.o,
|
|
404
|
+
last.h,
|
|
405
|
+
last.l,
|
|
406
|
+
last.c,
|
|
407
|
+
last.v ?? "",
|
|
408
|
+
prev?.time.getTime() ?? "",
|
|
409
|
+
prev?.c ?? "",
|
|
410
|
+
prev?.v ?? ""
|
|
411
|
+
].join("|");
|
|
412
|
+
};
|
|
413
|
+
var withCachedSeries = (key, data, compute) => {
|
|
414
|
+
const fingerprint = getSeriesFingerprint(data);
|
|
415
|
+
const existing = builtInSeriesCache.get(key);
|
|
416
|
+
if (existing && existing.fingerprint === fingerprint) {
|
|
417
|
+
return existing.values;
|
|
418
|
+
}
|
|
419
|
+
const values = compute();
|
|
420
|
+
builtInSeriesCache.set(key, { fingerprint, values });
|
|
421
|
+
return values;
|
|
422
|
+
};
|
|
389
423
|
var drawOverlaySeries = (ctx, renderContext, values, color, width) => {
|
|
390
424
|
if (!renderContext.yFromPrice) return;
|
|
391
425
|
const yFromPrice = renderContext.yFromPrice;
|
|
@@ -483,6 +517,8 @@ var BUILTIN_VOLUME_INDICATOR = {
|
|
|
483
517
|
defaultInputs: {
|
|
484
518
|
upOpacity: 0.7,
|
|
485
519
|
downOpacity: 0.7,
|
|
520
|
+
upColor: "",
|
|
521
|
+
downColor: "",
|
|
486
522
|
minBarWidth: 1,
|
|
487
523
|
overlayHeightRatio: 0.22
|
|
488
524
|
},
|
|
@@ -511,11 +547,13 @@ var BUILTIN_VOLUME_INDICATOR = {
|
|
|
511
547
|
const xCenter = xFromIndex(index);
|
|
512
548
|
const barX = Math.round(xCenter - barWidth / 2);
|
|
513
549
|
const barY = Math.round(paneBottom - volumeHeight);
|
|
514
|
-
const
|
|
515
|
-
const opacity =
|
|
550
|
+
const direction = renderContext.getCandleDirectionByIndex(index);
|
|
551
|
+
const opacity = direction === "up" ? upOpacity : downOpacity;
|
|
552
|
+
const upBarColor = inputs.upColor && inputs.upColor.trim().length > 0 ? inputs.upColor : upColor;
|
|
553
|
+
const downBarColor = inputs.downColor && inputs.downColor.trim().length > 0 ? inputs.downColor : downColor;
|
|
516
554
|
ctx.save();
|
|
517
555
|
ctx.globalAlpha = opacity;
|
|
518
|
-
ctx.fillStyle =
|
|
556
|
+
ctx.fillStyle = direction === "up" ? upBarColor : downBarColor;
|
|
519
557
|
ctx.fillRect(barX, barY, Math.max(1, Math.round(barWidth)), volumeHeight);
|
|
520
558
|
ctx.restore();
|
|
521
559
|
}
|
|
@@ -528,7 +566,11 @@ var BUILTIN_SMA_INDICATOR = {
|
|
|
528
566
|
defaultInputs: { length: 20, source: "close", color: "#60a5fa", width: 2 },
|
|
529
567
|
draw: (ctx, renderContext, inputs) => {
|
|
530
568
|
const length = clampIndicatorLength(inputs.length, 20);
|
|
531
|
-
const values =
|
|
569
|
+
const values = withCachedSeries(
|
|
570
|
+
`sma|${length}|${inputs.source ?? "close"}`,
|
|
571
|
+
renderContext.data,
|
|
572
|
+
() => computeSmaSeries(renderContext.data, length, inputs.source ?? "close")
|
|
573
|
+
);
|
|
532
574
|
drawOverlaySeries(ctx, renderContext, values, inputs.color ?? "#60a5fa", Number(inputs.width) || 2);
|
|
533
575
|
}
|
|
534
576
|
};
|
|
@@ -539,7 +581,11 @@ var BUILTIN_EMA_INDICATOR = {
|
|
|
539
581
|
defaultInputs: { length: 20, source: "close", color: "#f59e0b", width: 2 },
|
|
540
582
|
draw: (ctx, renderContext, inputs) => {
|
|
541
583
|
const length = clampIndicatorLength(inputs.length, 20);
|
|
542
|
-
const values =
|
|
584
|
+
const values = withCachedSeries(
|
|
585
|
+
`ema|${length}|${inputs.source ?? "close"}`,
|
|
586
|
+
renderContext.data,
|
|
587
|
+
() => computeEmaSeries(renderContext.data, length, inputs.source ?? "close")
|
|
588
|
+
);
|
|
543
589
|
drawOverlaySeries(ctx, renderContext, values, inputs.color ?? "#f59e0b", Number(inputs.width) || 2);
|
|
544
590
|
}
|
|
545
591
|
};
|
|
@@ -550,7 +596,11 @@ var BUILTIN_WMA_INDICATOR = {
|
|
|
550
596
|
defaultInputs: { length: 20, source: "close", color: "#a78bfa", width: 2 },
|
|
551
597
|
draw: (ctx, renderContext, inputs) => {
|
|
552
598
|
const length = clampIndicatorLength(inputs.length, 20);
|
|
553
|
-
const values =
|
|
599
|
+
const values = withCachedSeries(
|
|
600
|
+
`wma|${length}|${inputs.source ?? "close"}`,
|
|
601
|
+
renderContext.data,
|
|
602
|
+
() => computeWmaSeries(renderContext.data, length, inputs.source ?? "close")
|
|
603
|
+
);
|
|
554
604
|
drawOverlaySeries(ctx, renderContext, values, inputs.color ?? "#a78bfa", Number(inputs.width) || 2);
|
|
555
605
|
}
|
|
556
606
|
};
|
|
@@ -561,7 +611,11 @@ var BUILTIN_VWMA_INDICATOR = {
|
|
|
561
611
|
defaultInputs: { length: 20, source: "close", color: "#ef4444", width: 2 },
|
|
562
612
|
draw: (ctx, renderContext, inputs) => {
|
|
563
613
|
const length = clampIndicatorLength(inputs.length, 20);
|
|
564
|
-
const values =
|
|
614
|
+
const values = withCachedSeries(
|
|
615
|
+
`vwma|${length}|${inputs.source ?? "close"}`,
|
|
616
|
+
renderContext.data,
|
|
617
|
+
() => computeVwmaSeries(renderContext.data, length, inputs.source ?? "close")
|
|
618
|
+
);
|
|
565
619
|
drawOverlaySeries(ctx, renderContext, values, inputs.color ?? "#ef4444", Number(inputs.width) || 2);
|
|
566
620
|
}
|
|
567
621
|
};
|
|
@@ -572,7 +626,11 @@ var BUILTIN_RMA_INDICATOR = {
|
|
|
572
626
|
defaultInputs: { length: 14, source: "close", color: "#22c55e", width: 2 },
|
|
573
627
|
draw: (ctx, renderContext, inputs) => {
|
|
574
628
|
const length = clampIndicatorLength(inputs.length, 14);
|
|
575
|
-
const values =
|
|
629
|
+
const values = withCachedSeries(
|
|
630
|
+
`rma|${length}|${inputs.source ?? "close"}`,
|
|
631
|
+
renderContext.data,
|
|
632
|
+
() => computeRmaSeries(renderContext.data, length, inputs.source ?? "close")
|
|
633
|
+
);
|
|
576
634
|
drawOverlaySeries(ctx, renderContext, values, inputs.color ?? "#22c55e", Number(inputs.width) || 2);
|
|
577
635
|
}
|
|
578
636
|
};
|
|
@@ -583,7 +641,11 @@ var BUILTIN_HMA_INDICATOR = {
|
|
|
583
641
|
defaultInputs: { length: 21, source: "close", color: "#14b8a6", width: 2 },
|
|
584
642
|
draw: (ctx, renderContext, inputs) => {
|
|
585
643
|
const length = clampIndicatorLength(inputs.length, 21);
|
|
586
|
-
const values =
|
|
644
|
+
const values = withCachedSeries(
|
|
645
|
+
`hma|${length}|${inputs.source ?? "close"}`,
|
|
646
|
+
renderContext.data,
|
|
647
|
+
() => computeHmaSeries(renderContext.data, length, inputs.source ?? "close")
|
|
648
|
+
);
|
|
587
649
|
drawOverlaySeries(ctx, renderContext, values, inputs.color ?? "#14b8a6", Number(inputs.width) || 2);
|
|
588
650
|
}
|
|
589
651
|
};
|
|
@@ -595,7 +657,11 @@ var BUILTIN_STDDEV_INDICATOR = {
|
|
|
595
657
|
defaultInputs: { length: 20, source: "close", color: "#f97316", width: 2 },
|
|
596
658
|
draw: (ctx, renderContext, inputs) => {
|
|
597
659
|
const length = clampIndicatorLength(inputs.length, 20);
|
|
598
|
-
const values =
|
|
660
|
+
const values = withCachedSeries(
|
|
661
|
+
`stddev|${length}|${inputs.source ?? "close"}`,
|
|
662
|
+
renderContext.data,
|
|
663
|
+
() => computeStdDevSeries(renderContext.data, length, inputs.source ?? "close")
|
|
664
|
+
);
|
|
599
665
|
drawSeparateSeries(ctx, renderContext, values, inputs.color ?? "#f97316", Number(inputs.width) || 2);
|
|
600
666
|
}
|
|
601
667
|
};
|
|
@@ -607,7 +673,7 @@ var BUILTIN_ATR_INDICATOR = {
|
|
|
607
673
|
defaultInputs: { length: 14, color: "#eab308", width: 2 },
|
|
608
674
|
draw: (ctx, renderContext, inputs) => {
|
|
609
675
|
const length = clampIndicatorLength(inputs.length, 14);
|
|
610
|
-
const values = computeAtrSeries(renderContext.data, length);
|
|
676
|
+
const values = withCachedSeries(`atr|${length}`, renderContext.data, () => computeAtrSeries(renderContext.data, length));
|
|
611
677
|
drawSeparateSeries(ctx, renderContext, values, inputs.color ?? "#eab308", Number(inputs.width) || 2);
|
|
612
678
|
}
|
|
613
679
|
};
|
|
@@ -619,7 +685,7 @@ var BUILTIN_RSI_INDICATOR = {
|
|
|
619
685
|
defaultInputs: { length: 14, color: "#3b82f6", width: 2 },
|
|
620
686
|
draw: (ctx, renderContext, inputs) => {
|
|
621
687
|
const length = clampIndicatorLength(inputs.length, 14);
|
|
622
|
-
const values = computeRsiSeries(renderContext.data, length);
|
|
688
|
+
const values = withCachedSeries(`rsi|${length}`, renderContext.data, () => computeRsiSeries(renderContext.data, length));
|
|
623
689
|
drawSeparateSeries(
|
|
624
690
|
ctx,
|
|
625
691
|
renderContext,
|
|
@@ -653,6 +719,18 @@ function createChart(element, options = {}) {
|
|
|
653
719
|
...options.axis ?? {},
|
|
654
720
|
...options.axisColor ? { lineColor: options.axisColor, textColor: options.axisColor } : {}
|
|
655
721
|
},
|
|
722
|
+
xAxis: {
|
|
723
|
+
...DEFAULT_AXIS_OPTIONS,
|
|
724
|
+
...options.axis ?? {},
|
|
725
|
+
...options.axisColor ? { lineColor: options.axisColor, textColor: options.axisColor } : {},
|
|
726
|
+
...options.xAxis ?? {}
|
|
727
|
+
},
|
|
728
|
+
yAxis: {
|
|
729
|
+
...DEFAULT_AXIS_OPTIONS,
|
|
730
|
+
...options.axis ?? {},
|
|
731
|
+
...options.axisColor ? { lineColor: options.axisColor, textColor: options.axisColor } : {},
|
|
732
|
+
...options.yAxis ?? {}
|
|
733
|
+
},
|
|
656
734
|
crosshair: {
|
|
657
735
|
...DEFAULT_CROSSHAIR_OPTIONS,
|
|
658
736
|
...options.crosshair ?? {}
|
|
@@ -708,6 +786,9 @@ function createChart(element, options = {}) {
|
|
|
708
786
|
visible: indicator.visible ?? true,
|
|
709
787
|
pane: indicator.pane ?? plugin?.pane ?? "overlay",
|
|
710
788
|
...indicator.paneHeightRatio === void 0 ? {} : { paneHeightRatio: indicator.paneHeightRatio },
|
|
789
|
+
zIndex: Math.round(Number(indicator.zIndex) || 0),
|
|
790
|
+
excludeFromAutoscale: indicator.excludeFromAutoscale ?? true,
|
|
791
|
+
overlayScaleWeight: Math.min(1, Math.max(0, Number(indicator.overlayScaleWeight) || 0.25)),
|
|
711
792
|
inputs: {
|
|
712
793
|
...defaults,
|
|
713
794
|
...indicator.inputs ?? {}
|
|
@@ -863,9 +944,63 @@ function createChart(element, options = {}) {
|
|
|
863
944
|
}
|
|
864
945
|
return { min: nextMin, max: nextMax };
|
|
865
946
|
};
|
|
947
|
+
const getConfiguredPriceDecimals = () => clamp(Math.round(mergedOptions.priceDecimals), 0, 8);
|
|
948
|
+
const getConfiguredTickSize = () => {
|
|
949
|
+
const step = Number(mergedOptions.tickSize);
|
|
950
|
+
return Number.isFinite(step) && step > 0 ? step : 0;
|
|
951
|
+
};
|
|
952
|
+
const getTickSizeDecimals = () => {
|
|
953
|
+
const tickSize = getConfiguredTickSize();
|
|
954
|
+
if (tickSize <= 0) {
|
|
955
|
+
return 0;
|
|
956
|
+
}
|
|
957
|
+
let scaled = tickSize;
|
|
958
|
+
let decimals = 0;
|
|
959
|
+
while (decimals < 8 && Math.abs(Math.round(scaled) - scaled) > 1e-10) {
|
|
960
|
+
scaled *= 10;
|
|
961
|
+
decimals += 1;
|
|
962
|
+
}
|
|
963
|
+
return decimals;
|
|
964
|
+
};
|
|
965
|
+
const getDisplayPriceDecimals = () => {
|
|
966
|
+
const configured = getConfiguredPriceDecimals();
|
|
967
|
+
const tickDecimals = getTickSizeDecimals();
|
|
968
|
+
return Math.max(configured, tickDecimals);
|
|
969
|
+
};
|
|
970
|
+
const quantizeToTickSize = (price) => {
|
|
971
|
+
const tickSize = getConfiguredTickSize();
|
|
972
|
+
if (tickSize <= 0 || !Number.isFinite(price)) {
|
|
973
|
+
return price;
|
|
974
|
+
}
|
|
975
|
+
return Math.round(price / tickSize) * tickSize;
|
|
976
|
+
};
|
|
866
977
|
const formatPrice = (price) => {
|
|
867
|
-
const
|
|
868
|
-
|
|
978
|
+
const rounded = quantizeToTickSize(price);
|
|
979
|
+
const decimals = getDisplayPriceDecimals();
|
|
980
|
+
return rounded.toFixed(decimals);
|
|
981
|
+
};
|
|
982
|
+
const roundToPricePrecision = (price) => {
|
|
983
|
+
const rounded = quantizeToTickSize(price);
|
|
984
|
+
const decimals = getDisplayPriceDecimals();
|
|
985
|
+
return Number(rounded.toFixed(decimals));
|
|
986
|
+
};
|
|
987
|
+
const getResolvedCandleColorEpsilon = () => {
|
|
988
|
+
const configured = mergedOptions.candleColorEpsilon;
|
|
989
|
+
if (configured >= 0) {
|
|
990
|
+
return configured;
|
|
991
|
+
}
|
|
992
|
+
const tickSize = getConfiguredTickSize();
|
|
993
|
+
if (tickSize > 0) {
|
|
994
|
+
return tickSize / 2;
|
|
995
|
+
}
|
|
996
|
+
const decimals = getDisplayPriceDecimals();
|
|
997
|
+
return decimals > 0 ? 0.5 / 10 ** decimals : 0;
|
|
998
|
+
};
|
|
999
|
+
const getDirectionFromDelta = (delta) => {
|
|
1000
|
+
const epsilon = Math.max(0, getResolvedCandleColorEpsilon());
|
|
1001
|
+
if (delta > epsilon) return 1;
|
|
1002
|
+
if (delta < -epsilon) return -1;
|
|
1003
|
+
return 0;
|
|
869
1004
|
};
|
|
870
1005
|
const getStabilizedPriceTemplate = () => {
|
|
871
1006
|
const explicitTemplate = mergedOptions.priceLabelWidthTemplate.trim();
|
|
@@ -893,14 +1028,33 @@ function createChart(element, options = {}) {
|
|
|
893
1028
|
return Math.max(measured, templateWidth);
|
|
894
1029
|
};
|
|
895
1030
|
const parseData = (nextData) => {
|
|
896
|
-
|
|
897
|
-
|
|
898
|
-
|
|
899
|
-
|
|
900
|
-
|
|
901
|
-
|
|
902
|
-
|
|
903
|
-
|
|
1031
|
+
const dedupedByTime = /* @__PURE__ */ new Map();
|
|
1032
|
+
for (const point of nextData) {
|
|
1033
|
+
const time = new Date(point.t);
|
|
1034
|
+
const timeMs = time.getTime();
|
|
1035
|
+
if (!Number.isFinite(timeMs)) {
|
|
1036
|
+
continue;
|
|
1037
|
+
}
|
|
1038
|
+
const open = Number(point.o);
|
|
1039
|
+
const close = Number(point.c);
|
|
1040
|
+
const highInput = Number(point.h);
|
|
1041
|
+
const lowInput = Number(point.l);
|
|
1042
|
+
if (!Number.isFinite(open) || !Number.isFinite(close) || !Number.isFinite(highInput) || !Number.isFinite(lowInput)) {
|
|
1043
|
+
continue;
|
|
1044
|
+
}
|
|
1045
|
+
const normalizedHigh = Math.max(highInput, open, close);
|
|
1046
|
+
const normalizedLow = Math.min(lowInput, open, close);
|
|
1047
|
+
const volumeValue = point.v === void 0 ? void 0 : Number(point.v);
|
|
1048
|
+
dedupedByTime.set(timeMs, {
|
|
1049
|
+
time,
|
|
1050
|
+
o: open,
|
|
1051
|
+
h: normalizedHigh,
|
|
1052
|
+
l: normalizedLow,
|
|
1053
|
+
c: close,
|
|
1054
|
+
...volumeValue === void 0 || !Number.isFinite(volumeValue) ? {} : { v: volumeValue }
|
|
1055
|
+
});
|
|
1056
|
+
}
|
|
1057
|
+
return Array.from(dedupedByTime.values()).sort((a, b) => a.time.getTime() - b.time.getTime());
|
|
904
1058
|
};
|
|
905
1059
|
const getTimeStepMs = () => {
|
|
906
1060
|
if (data.length < 2) {
|
|
@@ -977,6 +1131,26 @@ function createChart(element, options = {}) {
|
|
|
977
1131
|
const upperDelta = Math.abs(upperPoint.time.getTime() - timeMs);
|
|
978
1132
|
return lowerDelta <= upperDelta ? lower : upper;
|
|
979
1133
|
};
|
|
1134
|
+
const getCandleDirectionByIndex = (index) => {
|
|
1135
|
+
const point = data[index];
|
|
1136
|
+
if (!point) {
|
|
1137
|
+
return "up";
|
|
1138
|
+
}
|
|
1139
|
+
const prevPoint = index > 0 ? data[index - 1] : void 0;
|
|
1140
|
+
const mode = mergedOptions.candleColorMode;
|
|
1141
|
+
const baseForMode = mode === "prevClose" && prevPoint ? prevPoint.c : point.o;
|
|
1142
|
+
let direction = getDirectionFromDelta(point.c - baseForMode);
|
|
1143
|
+
if (direction === 0 && mode === "prevClose") {
|
|
1144
|
+
direction = getDirectionFromDelta(point.c - point.o);
|
|
1145
|
+
}
|
|
1146
|
+
if (direction === 0 && prevPoint) {
|
|
1147
|
+
direction = getDirectionFromDelta(point.c - prevPoint.c);
|
|
1148
|
+
}
|
|
1149
|
+
if (direction === 0) {
|
|
1150
|
+
return point.c >= point.o ? "up" : "down";
|
|
1151
|
+
}
|
|
1152
|
+
return direction > 0 ? "up" : "down";
|
|
1153
|
+
};
|
|
980
1154
|
const formatHoverTimeLabel = (time, mode) => {
|
|
981
1155
|
if (mode === "time") {
|
|
982
1156
|
return time.toLocaleTimeString(void 0, {
|
|
@@ -1351,6 +1525,10 @@ function createChart(element, options = {}) {
|
|
|
1351
1525
|
canvas.height = Math.floor(height * pixelRatio);
|
|
1352
1526
|
ctx.setTransform(pixelRatio, 0, 0, pixelRatio, 0, 0);
|
|
1353
1527
|
const axis = { ...DEFAULT_AXIS_OPTIONS, ...mergedOptions.axis ?? {} };
|
|
1528
|
+
const xAxis = { ...DEFAULT_AXIS_OPTIONS, ...mergedOptions.xAxis ?? {} };
|
|
1529
|
+
const yAxis = { ...DEFAULT_AXIS_OPTIONS, ...mergedOptions.yAxis ?? {} };
|
|
1530
|
+
const xAxisFontSize = Math.max(8, xAxis.fontSize);
|
|
1531
|
+
const yAxisFontSize = Math.max(8, yAxis.fontSize);
|
|
1354
1532
|
ctx.font = `${Math.max(8, axis.fontSize)}px ${mergedOptions.fontFamily}`;
|
|
1355
1533
|
ctx.fillStyle = mergedOptions.backgroundColor;
|
|
1356
1534
|
ctx.fillRect(0, 0, width, height);
|
|
@@ -1365,6 +1543,9 @@ function createChart(element, options = {}) {
|
|
|
1365
1543
|
const separatePaneSpacing = 6;
|
|
1366
1544
|
const activeSeparateIndicators = indicators.filter((indicator) => indicator.visible).map((indicator) => ({ indicator, plugin: indicatorRegistry.get(indicator.type) })).filter(
|
|
1367
1545
|
(value) => value.plugin !== void 0 && (value.indicator.pane ?? value.plugin.pane ?? "overlay") === "separate"
|
|
1546
|
+
).sort((a, b) => a.indicator.zIndex - b.indicator.zIndex);
|
|
1547
|
+
const overlayIndicatorsForScale = indicators.filter((indicator) => indicator.visible).map((indicator) => ({ indicator, plugin: indicatorRegistry.get(indicator.type) })).filter(
|
|
1548
|
+
(value) => value.plugin !== void 0 && (value.indicator.pane ?? value.plugin.pane ?? "overlay") === "overlay"
|
|
1368
1549
|
);
|
|
1369
1550
|
const separatePaneHeightDefaults = activeSeparateIndicators.map(({ indicator, plugin }) => {
|
|
1370
1551
|
const ratio = Math.min(0.45, Math.max(0.08, indicator.paneHeightRatio ?? plugin.paneHeightRatio ?? 0.22));
|
|
@@ -1416,8 +1597,47 @@ function createChart(element, options = {}) {
|
|
|
1416
1597
|
}
|
|
1417
1598
|
}
|
|
1418
1599
|
}
|
|
1419
|
-
|
|
1420
|
-
|
|
1600
|
+
let minPrice = Math.min(...priceSource.map((point) => point.l));
|
|
1601
|
+
let maxPrice = Math.max(...priceSource.map((point) => point.h));
|
|
1602
|
+
if (overlayIndicatorsForScale.length > 0) {
|
|
1603
|
+
for (const { indicator } of overlayIndicatorsForScale) {
|
|
1604
|
+
if (indicator.excludeFromAutoscale) {
|
|
1605
|
+
continue;
|
|
1606
|
+
}
|
|
1607
|
+
const type = indicator.type;
|
|
1608
|
+
const inputs = indicator.inputs;
|
|
1609
|
+
const source = inputs.source ?? "close";
|
|
1610
|
+
const length = clampIndicatorLength(inputs.length ?? 14, 14);
|
|
1611
|
+
let series = null;
|
|
1612
|
+
if (type === "sma") series = withCachedSeries(`sma|${length}|${source}`, data, () => computeSmaSeries(data, length, source));
|
|
1613
|
+
if (type === "ema") series = withCachedSeries(`ema|${length}|${source}`, data, () => computeEmaSeries(data, length, source));
|
|
1614
|
+
if (type === "wma") series = withCachedSeries(`wma|${length}|${source}`, data, () => computeWmaSeries(data, length, source));
|
|
1615
|
+
if (type === "vwma") series = withCachedSeries(`vwma|${length}|${source}`, data, () => computeVwmaSeries(data, length, source));
|
|
1616
|
+
if (type === "rma") series = withCachedSeries(`rma|${length}|${source}`, data, () => computeRmaSeries(data, length, source));
|
|
1617
|
+
if (type === "hma") series = withCachedSeries(`hma|${length}|${source}`, data, () => computeHmaSeries(data, length, source));
|
|
1618
|
+
if (!series) {
|
|
1619
|
+
continue;
|
|
1620
|
+
}
|
|
1621
|
+
const visibleValues = [];
|
|
1622
|
+
for (let idx = startIndex; idx <= endIndex; idx += 1) {
|
|
1623
|
+
const value = series[idx];
|
|
1624
|
+
if (Number.isFinite(value ?? Number.NaN)) {
|
|
1625
|
+
visibleValues.push(value);
|
|
1626
|
+
}
|
|
1627
|
+
}
|
|
1628
|
+
if (visibleValues.length === 0) {
|
|
1629
|
+
continue;
|
|
1630
|
+
}
|
|
1631
|
+
const seriesMin = Math.min(...visibleValues);
|
|
1632
|
+
const seriesMax = Math.max(...visibleValues);
|
|
1633
|
+
const weight = Math.min(1, Math.max(0, indicator.overlayScaleWeight));
|
|
1634
|
+
const currentMid = (minPrice + maxPrice) / 2;
|
|
1635
|
+
const weightedMin = currentMid + (seriesMin - currentMid) * weight;
|
|
1636
|
+
const weightedMax = currentMid + (seriesMax - currentMid) * weight;
|
|
1637
|
+
minPrice = Math.min(minPrice, weightedMin);
|
|
1638
|
+
maxPrice = Math.max(maxPrice, weightedMax);
|
|
1639
|
+
}
|
|
1640
|
+
}
|
|
1421
1641
|
const priceRange = maxPrice - minPrice || 1;
|
|
1422
1642
|
const autoMin = minPrice - priceRange * 0.08;
|
|
1423
1643
|
const autoMax = maxPrice + priceRange * 0.08;
|
|
@@ -1562,8 +1782,8 @@ function createChart(element, options = {}) {
|
|
|
1562
1782
|
const closeY = yFromPrice(point.c);
|
|
1563
1783
|
const highY = yFromPrice(point.h);
|
|
1564
1784
|
const lowY = yFromPrice(point.l);
|
|
1565
|
-
const
|
|
1566
|
-
const candleColor =
|
|
1785
|
+
const direction = getCandleDirectionByIndex(index);
|
|
1786
|
+
const candleColor = direction === "up" ? mergedOptions.upColor : mergedOptions.downColor;
|
|
1567
1787
|
ctx.strokeStyle = candleColor;
|
|
1568
1788
|
ctx.lineWidth = candleWickWidth;
|
|
1569
1789
|
ctx.beginPath();
|
|
@@ -1577,7 +1797,7 @@ function createChart(element, options = {}) {
|
|
|
1577
1797
|
}
|
|
1578
1798
|
const activeOverlayIndicators = indicators.filter((indicator) => indicator.visible).map((indicator) => ({ indicator, plugin: indicatorRegistry.get(indicator.type) })).filter(
|
|
1579
1799
|
(value) => value.plugin !== void 0 && (value.indicator.pane ?? value.plugin.pane ?? "overlay") === "overlay"
|
|
1580
|
-
);
|
|
1800
|
+
).sort((a, b) => a.indicator.zIndex - b.indicator.zIndex);
|
|
1581
1801
|
if (activeOverlayIndicators.length > 0) {
|
|
1582
1802
|
const xFromIndex = (index) => chartLeft + (index + 0.5 - xStart) / xSpan * chartWidth;
|
|
1583
1803
|
activeOverlayIndicators.forEach(({ indicator, plugin }) => {
|
|
@@ -1597,6 +1817,7 @@ function createChart(element, options = {}) {
|
|
|
1597
1817
|
chartHeight,
|
|
1598
1818
|
xFromIndex,
|
|
1599
1819
|
yFromPrice,
|
|
1820
|
+
getCandleDirectionByIndex,
|
|
1600
1821
|
candleSpacing,
|
|
1601
1822
|
upColor: mergedOptions.upColor,
|
|
1602
1823
|
downColor: mergedOptions.downColor
|
|
@@ -1656,6 +1877,7 @@ function createChart(element, options = {}) {
|
|
|
1656
1877
|
chartHeight: paneHeight,
|
|
1657
1878
|
xFromIndex,
|
|
1658
1879
|
yFromPrice: null,
|
|
1880
|
+
getCandleDirectionByIndex,
|
|
1659
1881
|
candleSpacing,
|
|
1660
1882
|
upColor: mergedOptions.upColor,
|
|
1661
1883
|
downColor: mergedOptions.downColor
|
|
@@ -1685,7 +1907,10 @@ function createChart(element, options = {}) {
|
|
|
1685
1907
|
const ratio = tick / yTicks;
|
|
1686
1908
|
const price = yMin + yRange * ratio;
|
|
1687
1909
|
const y = yFromPrice(price);
|
|
1688
|
-
|
|
1910
|
+
const prevFont = ctx.font;
|
|
1911
|
+
ctx.font = `${yAxisFontSize}px ${mergedOptions.fontFamily}`;
|
|
1912
|
+
drawText(formatPrice(price), chartRight + 6, y, "left", "middle", yAxis.textColor);
|
|
1913
|
+
ctx.font = prevFont;
|
|
1689
1914
|
}
|
|
1690
1915
|
const ticker = mergedOptions.tickerLine ?? DEFAULT_OPTIONS.tickerLine;
|
|
1691
1916
|
const lastPoint = data[data.length - 1];
|
|
@@ -1693,7 +1918,8 @@ function createChart(element, options = {}) {
|
|
|
1693
1918
|
const tickerPrice = lastPoint.c;
|
|
1694
1919
|
const tickerY = yFromPrice(tickerPrice);
|
|
1695
1920
|
const lineY = clamp(tickerY, chartTop + 1, chartBottom - 1);
|
|
1696
|
-
const
|
|
1921
|
+
const lastDirection = getCandleDirectionByIndex(data.length - 1);
|
|
1922
|
+
const tickerColor = ticker.color ?? (lastDirection === "up" ? mergedOptions.upColor : mergedOptions.downColor);
|
|
1697
1923
|
const tickerThickness = Math.max(1, ticker.thickness ?? 1);
|
|
1698
1924
|
const tickerStyle = ticker.style ?? "solid";
|
|
1699
1925
|
ctx.save();
|
|
@@ -1741,7 +1967,10 @@ function createChart(element, options = {}) {
|
|
|
1741
1967
|
month: "short",
|
|
1742
1968
|
day: "numeric"
|
|
1743
1969
|
});
|
|
1744
|
-
|
|
1970
|
+
const prevFont = ctx.font;
|
|
1971
|
+
ctx.font = `${xAxisFontSize}px ${mergedOptions.fontFamily}`;
|
|
1972
|
+
drawText(timeLabel, x, fullChartBottom + 8, "center", "top", xAxis.textColor);
|
|
1973
|
+
ctx.font = prevFont;
|
|
1745
1974
|
}
|
|
1746
1975
|
if (crosshair.visible && crosshairPoint) {
|
|
1747
1976
|
const cx = clamp(crosshairPoint.x, chartLeft, chartRight);
|
|
@@ -2061,7 +2290,7 @@ function createChart(element, options = {}) {
|
|
|
2061
2290
|
x,
|
|
2062
2291
|
y,
|
|
2063
2292
|
region,
|
|
2064
|
-
...region === "plot" ? { price:
|
|
2293
|
+
...region === "plot" ? { price: roundToPricePrecision(priceFromCanvasY(y)) } : {},
|
|
2065
2294
|
...index === null ? {} : { index },
|
|
2066
2295
|
...hoverTime ? { time: hoverTime.toISOString() } : {},
|
|
2067
2296
|
...point ? { point } : {}
|
|
@@ -2107,7 +2336,7 @@ function createChart(element, options = {}) {
|
|
|
2107
2336
|
if (orderRegion) {
|
|
2108
2337
|
if (orderRegion.draggable) {
|
|
2109
2338
|
activePointerId = event.pointerId;
|
|
2110
|
-
const startPrice =
|
|
2339
|
+
const startPrice = roundToPricePrecision(orderRegion.line.price);
|
|
2111
2340
|
actionDragState = {
|
|
2112
2341
|
orderId: orderRegion.orderId,
|
|
2113
2342
|
action: orderRegion.action,
|
|
@@ -2166,7 +2395,7 @@ function createChart(element, options = {}) {
|
|
|
2166
2395
|
if (activePointerId !== null && event.pointerId !== activePointerId) {
|
|
2167
2396
|
return;
|
|
2168
2397
|
}
|
|
2169
|
-
const nextPrice =
|
|
2398
|
+
const nextPrice = roundToPricePrecision(priceFromCanvasY(point.y));
|
|
2170
2399
|
if (nextPrice !== orderDragState.lastPrice) {
|
|
2171
2400
|
orderDragState.lastPrice = nextPrice;
|
|
2172
2401
|
orderLines = orderLines.map((line) => {
|
|
@@ -2196,7 +2425,7 @@ function createChart(element, options = {}) {
|
|
|
2196
2425
|
if (activePointerId !== null && event.pointerId !== activePointerId) {
|
|
2197
2426
|
return;
|
|
2198
2427
|
}
|
|
2199
|
-
const nextPrice =
|
|
2428
|
+
const nextPrice = roundToPricePrecision(priceFromCanvasY(point.y));
|
|
2200
2429
|
if (nextPrice !== actionDragState.lastPrice) {
|
|
2201
2430
|
actionDragState.lastPrice = nextPrice;
|
|
2202
2431
|
actionDragState.moved = true;
|
|
@@ -2313,7 +2542,7 @@ function createChart(element, options = {}) {
|
|
|
2313
2542
|
canvas.style.cursor = "default";
|
|
2314
2543
|
if (event && pointerDownInfo && event.pointerId === pointerDownInfo.pointerId) {
|
|
2315
2544
|
if (!pointerDownInfo.moved) {
|
|
2316
|
-
const clickPrice = pointerDownInfo.region === "plot" ?
|
|
2545
|
+
const clickPrice = pointerDownInfo.region === "plot" ? roundToPricePrecision(priceFromCanvasY(pointerDownInfo.y)) : void 0;
|
|
2317
2546
|
chartClickHandler?.({
|
|
2318
2547
|
x: pointerDownInfo.x,
|
|
2319
2548
|
y: pointerDownInfo.y,
|
|
@@ -2373,7 +2602,7 @@ function createChart(element, options = {}) {
|
|
|
2373
2602
|
}
|
|
2374
2603
|
orderActionHandler?.({
|
|
2375
2604
|
action: "createLimit",
|
|
2376
|
-
price:
|
|
2605
|
+
price: roundToPricePrecision(priceFromCanvasY(point.y))
|
|
2377
2606
|
});
|
|
2378
2607
|
return;
|
|
2379
2608
|
}
|
|
@@ -2512,6 +2741,27 @@ function createChart(element, options = {}) {
|
|
|
2512
2741
|
indicators = indicators.filter((indicator) => indicator.type !== type);
|
|
2513
2742
|
draw();
|
|
2514
2743
|
};
|
|
2744
|
+
const listBuiltInIndicators = () => {
|
|
2745
|
+
return BUILTIN_INDICATORS.map((indicator) => ({
|
|
2746
|
+
id: indicator.id,
|
|
2747
|
+
name: indicator.name,
|
|
2748
|
+
pane: indicator.pane ?? "overlay",
|
|
2749
|
+
...indicator.paneHeightRatio === void 0 ? {} : { paneHeightRatio: indicator.paneHeightRatio }
|
|
2750
|
+
}));
|
|
2751
|
+
};
|
|
2752
|
+
const getIndicators = () => {
|
|
2753
|
+
return indicators.map((indicator) => ({
|
|
2754
|
+
id: indicator.id,
|
|
2755
|
+
type: indicator.type,
|
|
2756
|
+
visible: indicator.visible,
|
|
2757
|
+
pane: indicator.pane,
|
|
2758
|
+
...indicator.paneHeightRatio === void 0 ? {} : { paneHeightRatio: indicator.paneHeightRatio },
|
|
2759
|
+
zIndex: indicator.zIndex,
|
|
2760
|
+
excludeFromAutoscale: indicator.excludeFromAutoscale,
|
|
2761
|
+
overlayScaleWeight: indicator.overlayScaleWeight,
|
|
2762
|
+
inputs: { ...indicator.inputs }
|
|
2763
|
+
}));
|
|
2764
|
+
};
|
|
2515
2765
|
const setIndicators = (nextIndicators) => {
|
|
2516
2766
|
indicators = nextIndicators.map((indicator) => normalizeIndicatorState(indicator));
|
|
2517
2767
|
draw();
|
|
@@ -2541,6 +2791,9 @@ function createChart(element, options = {}) {
|
|
|
2541
2791
|
visible: patch.visible ?? indicator.visible,
|
|
2542
2792
|
pane: patch.pane ?? indicator.pane ?? plugin?.pane ?? "overlay",
|
|
2543
2793
|
...patch.paneHeightRatio !== void 0 || indicator.paneHeightRatio !== void 0 ? { paneHeightRatio: patch.paneHeightRatio ?? indicator.paneHeightRatio } : {},
|
|
2794
|
+
zIndex: patch.zIndex === void 0 ? indicator.zIndex : Math.round(Number(patch.zIndex) || 0),
|
|
2795
|
+
excludeFromAutoscale: patch.excludeFromAutoscale ?? indicator.excludeFromAutoscale,
|
|
2796
|
+
overlayScaleWeight: patch.overlayScaleWeight === void 0 ? indicator.overlayScaleWeight : Math.min(1, Math.max(0, Number(patch.overlayScaleWeight) || 0)),
|
|
2544
2797
|
type: patch.type ?? indicator.type,
|
|
2545
2798
|
inputs: {
|
|
2546
2799
|
...indicator.inputs,
|
|
@@ -2590,6 +2843,8 @@ function createChart(element, options = {}) {
|
|
|
2590
2843
|
setDoubleClickAction,
|
|
2591
2844
|
registerIndicator,
|
|
2592
2845
|
unregisterIndicator,
|
|
2846
|
+
listBuiltInIndicators,
|
|
2847
|
+
getIndicators,
|
|
2593
2848
|
addIndicator,
|
|
2594
2849
|
updateIndicator,
|
|
2595
2850
|
removeIndicator,
|
package/dist/index.d.cts
CHANGED
|
@@ -4,6 +4,8 @@ interface ChartOptions {
|
|
|
4
4
|
backgroundColor?: string;
|
|
5
5
|
axisColor?: string;
|
|
6
6
|
axis?: AxisOptions;
|
|
7
|
+
xAxis?: AxisOptions;
|
|
8
|
+
yAxis?: AxisOptions;
|
|
7
9
|
priceDecimals?: number;
|
|
8
10
|
stabilizePriceLabels?: boolean;
|
|
9
11
|
priceLabelMinIntegerDigits?: number;
|
|
@@ -22,6 +24,9 @@ interface ChartOptions {
|
|
|
22
24
|
candleBodyWidthRatio?: number;
|
|
23
25
|
candleMinWidth?: number;
|
|
24
26
|
candleWickWidth?: number;
|
|
27
|
+
tickSize?: number;
|
|
28
|
+
candleColorMode?: "openClose" | "prevClose";
|
|
29
|
+
candleColorEpsilon?: number;
|
|
25
30
|
autoScaleSmoothing?: number;
|
|
26
31
|
autoScaleIgnoreLatestCandle?: boolean;
|
|
27
32
|
doubleClickEnabled?: boolean;
|
|
@@ -42,6 +47,9 @@ interface IndicatorInstanceOptions<TInputs extends Record<string, unknown> = Rec
|
|
|
42
47
|
visible?: boolean;
|
|
43
48
|
pane?: IndicatorPane;
|
|
44
49
|
paneHeightRatio?: number;
|
|
50
|
+
zIndex?: number;
|
|
51
|
+
excludeFromAutoscale?: boolean;
|
|
52
|
+
overlayScaleWeight?: number;
|
|
45
53
|
inputs?: Partial<TInputs>;
|
|
46
54
|
}
|
|
47
55
|
interface IndicatorRenderContext {
|
|
@@ -65,6 +73,7 @@ interface IndicatorRenderContext {
|
|
|
65
73
|
chartHeight: number;
|
|
66
74
|
xFromIndex: (index: number) => number;
|
|
67
75
|
yFromPrice: ((price: number) => number) | null;
|
|
76
|
+
getCandleDirectionByIndex: (index: number) => "up" | "down";
|
|
68
77
|
candleSpacing: number;
|
|
69
78
|
upColor: string;
|
|
70
79
|
downColor: string;
|
|
@@ -77,6 +86,12 @@ interface IndicatorPlugin<TInputs extends Record<string, unknown> = Record<strin
|
|
|
77
86
|
defaultInputs?: TInputs;
|
|
78
87
|
draw: (ctx: CanvasRenderingContext2D, renderContext: IndicatorRenderContext, inputs: TInputs) => void;
|
|
79
88
|
}
|
|
89
|
+
interface BuiltInIndicatorInfo {
|
|
90
|
+
id: string;
|
|
91
|
+
name: string;
|
|
92
|
+
pane: IndicatorPane;
|
|
93
|
+
paneHeightRatio?: number;
|
|
94
|
+
}
|
|
80
95
|
interface DashPatternOptions {
|
|
81
96
|
dotted: [number, number];
|
|
82
97
|
dashed: [number, number];
|
|
@@ -268,6 +283,8 @@ interface ChartInstance {
|
|
|
268
283
|
setDoubleClickAction: (action: "reset" | "placeLimitOrder") => void;
|
|
269
284
|
registerIndicator: (plugin: IndicatorPlugin<any>) => void;
|
|
270
285
|
unregisterIndicator: (type: string) => void;
|
|
286
|
+
listBuiltInIndicators: () => BuiltInIndicatorInfo[];
|
|
287
|
+
getIndicators: () => IndicatorInstanceOptions[];
|
|
271
288
|
addIndicator: (type: string, inputs?: Record<string, unknown>, options?: Partial<IndicatorInstanceOptions>) => string;
|
|
272
289
|
updateIndicator: (id: string, patch: Partial<IndicatorInstanceOptions>) => void;
|
|
273
290
|
removeIndicator: (id: string) => void;
|
|
@@ -285,4 +302,4 @@ interface OhlcDataPoint {
|
|
|
285
302
|
}
|
|
286
303
|
declare function createChart(element: HTMLElement, options?: ChartOptions): ChartInstance;
|
|
287
304
|
|
|
288
|
-
export { type AxisOptions, type ChartClickEvent, type ChartInstance, type ChartOptions, type CrosshairMoveEvent, type CrosshairOptions, type CrosshairPriceActionEvent, type DashPatternOptions, type GridOptions, type IndicatorInstanceOptions, type IndicatorPane, type IndicatorPlugin, type IndicatorRenderContext, type OhlcDataPoint, type OrderActionButton, type OrderActionEvent, type OrderLineOptions, type PriceLineOptions, type TickerLineOptions, type WatermarkOptions, createChart };
|
|
305
|
+
export { type AxisOptions, type BuiltInIndicatorInfo, type ChartClickEvent, type ChartInstance, type ChartOptions, type CrosshairMoveEvent, type CrosshairOptions, type CrosshairPriceActionEvent, type DashPatternOptions, type GridOptions, type IndicatorInstanceOptions, type IndicatorPane, type IndicatorPlugin, type IndicatorRenderContext, type OhlcDataPoint, type OrderActionButton, type OrderActionEvent, type OrderLineOptions, type PriceLineOptions, type TickerLineOptions, type WatermarkOptions, createChart };
|