hyperprop-charting-library 0.1.138 → 0.1.139
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/hyperprop-charting-library.cjs +337 -1
- package/dist/hyperprop-charting-library.d.ts +52 -1
- package/dist/hyperprop-charting-library.js +337 -1
- package/dist/index.cjs +337 -1
- package/dist/index.d.cts +52 -1
- package/dist/index.d.ts +52 -1
- package/dist/index.js +337 -1
- package/docs/API.md +51 -0
- package/package.json +1 -1
|
@@ -2853,6 +2853,7 @@ var DEFAULT_OPTIONS = {
|
|
|
2853
2853
|
doubleClickAction: "reset",
|
|
2854
2854
|
keyboard: { enabled: true, panBars: 1, deleteSelectedDrawing: true },
|
|
2855
2855
|
accessibility: { label: "Price chart", description: "", focusable: true },
|
|
2856
|
+
downsampling: { enabled: true, thresholdPx: 1.5 },
|
|
2856
2857
|
crosshair: DEFAULT_CROSSHAIR_OPTIONS,
|
|
2857
2858
|
grid: DEFAULT_GRID_OPTIONS,
|
|
2858
2859
|
watermark: DEFAULT_WATERMARK_OPTIONS,
|
|
@@ -2903,6 +2904,10 @@ var mergeChartOptions = (baseOptions, options = {}) => ({
|
|
|
2903
2904
|
...baseOptions.accessibility,
|
|
2904
2905
|
...options.accessibility ?? {}
|
|
2905
2906
|
},
|
|
2907
|
+
downsampling: {
|
|
2908
|
+
...baseOptions.downsampling,
|
|
2909
|
+
...options.downsampling ?? {}
|
|
2910
|
+
},
|
|
2906
2911
|
crosshair: {
|
|
2907
2912
|
...baseOptions.crosshair,
|
|
2908
2913
|
...options.crosshair ?? {}
|
|
@@ -3778,6 +3783,64 @@ function createChart(element, options = {}) {
|
|
|
3778
3783
|
heikinAshiFingerprint = fingerprint;
|
|
3779
3784
|
return result;
|
|
3780
3785
|
};
|
|
3786
|
+
let compareSeriesStates = [];
|
|
3787
|
+
const prepareCompareSeries = (options2) => {
|
|
3788
|
+
const rows = [];
|
|
3789
|
+
for (const point of options2.data ?? []) {
|
|
3790
|
+
const t = new Date(point.t).getTime();
|
|
3791
|
+
const c = Number(point.c);
|
|
3792
|
+
if (Number.isFinite(t) && Number.isFinite(c)) rows.push({ t, c });
|
|
3793
|
+
}
|
|
3794
|
+
rows.sort((a, b) => a.t - b.t);
|
|
3795
|
+
return {
|
|
3796
|
+
options: options2,
|
|
3797
|
+
times: rows.map((row) => row.t),
|
|
3798
|
+
closes: rows.map((row) => row.c),
|
|
3799
|
+
aligned: null,
|
|
3800
|
+
alignedFingerprint: ""
|
|
3801
|
+
};
|
|
3802
|
+
};
|
|
3803
|
+
const mainDataFingerprint = () => {
|
|
3804
|
+
const last = data[data.length - 1];
|
|
3805
|
+
return last ? `${data.length}|${data[0].time.getTime()}|${last.time.getTime()}` : "empty";
|
|
3806
|
+
};
|
|
3807
|
+
const getAlignedCompareValues = (state) => {
|
|
3808
|
+
const fingerprint = `${mainDataFingerprint()}|${state.times.length}|${state.times[state.times.length - 1] ?? 0}`;
|
|
3809
|
+
if (state.aligned && state.alignedFingerprint === fingerprint) {
|
|
3810
|
+
return state.aligned;
|
|
3811
|
+
}
|
|
3812
|
+
const aligned = new Float64Array(data.length).fill(Number.NaN);
|
|
3813
|
+
let cursor = 0;
|
|
3814
|
+
let lastValue = Number.NaN;
|
|
3815
|
+
for (let i = 0; i < data.length; i += 1) {
|
|
3816
|
+
const barTime = data[i].time.getTime();
|
|
3817
|
+
while (cursor < state.times.length && state.times[cursor] <= barTime) {
|
|
3818
|
+
lastValue = state.closes[cursor];
|
|
3819
|
+
cursor += 1;
|
|
3820
|
+
}
|
|
3821
|
+
aligned[i] = lastValue;
|
|
3822
|
+
}
|
|
3823
|
+
state.aligned = aligned;
|
|
3824
|
+
state.alignedFingerprint = fingerprint;
|
|
3825
|
+
return aligned;
|
|
3826
|
+
};
|
|
3827
|
+
const visibleCompareSeries = () => compareSeriesStates.filter((state) => state.options.visible !== false && state.times.length > 0);
|
|
3828
|
+
const setCompareSeries = (series) => {
|
|
3829
|
+
compareSeriesStates = (series ?? []).filter((entry) => entry && entry.id).map(prepareCompareSeries);
|
|
3830
|
+
scheduleDraw({ updateAutoScale: true });
|
|
3831
|
+
};
|
|
3832
|
+
const addCompareSeries = (series) => {
|
|
3833
|
+
if (!series?.id) return;
|
|
3834
|
+
compareSeriesStates = [...compareSeriesStates.filter((state) => state.options.id !== series.id), prepareCompareSeries(series)];
|
|
3835
|
+
scheduleDraw({ updateAutoScale: true });
|
|
3836
|
+
};
|
|
3837
|
+
const removeCompareSeries = (id) => {
|
|
3838
|
+
const next = compareSeriesStates.filter((state) => state.options.id !== id);
|
|
3839
|
+
if (next.length === compareSeriesStates.length) return;
|
|
3840
|
+
compareSeriesStates = next;
|
|
3841
|
+
scheduleDraw({ updateAutoScale: true });
|
|
3842
|
+
};
|
|
3843
|
+
const getCompareSeries = () => compareSeriesStates.map((state) => state.options);
|
|
3781
3844
|
const formatHoverTimeLabel = (time, mode) => {
|
|
3782
3845
|
if (mode === "time") {
|
|
3783
3846
|
return time.toLocaleTimeString(void 0, {
|
|
@@ -4383,6 +4446,31 @@ function createChart(element, options = {}) {
|
|
|
4383
4446
|
const chartType = mergedOptions.chartType;
|
|
4384
4447
|
const seriesData = chartType === "heikin-ashi" ? getHeikinAshiData() : data;
|
|
4385
4448
|
const isLineStyleChart = chartType === "line" || chartType === "area" || chartType === "baseline";
|
|
4449
|
+
const comparePlans = [];
|
|
4450
|
+
for (const state of visibleCompareSeries()) {
|
|
4451
|
+
const aligned = getAlignedCompareValues(state);
|
|
4452
|
+
let anchorIndex = -1;
|
|
4453
|
+
for (let index = startIndex; index <= endIndex; index += 1) {
|
|
4454
|
+
if (Number.isFinite(aligned[index] ?? Number.NaN)) {
|
|
4455
|
+
anchorIndex = index;
|
|
4456
|
+
break;
|
|
4457
|
+
}
|
|
4458
|
+
}
|
|
4459
|
+
if (anchorIndex < 0) continue;
|
|
4460
|
+
const baseCompare = aligned[anchorIndex];
|
|
4461
|
+
const baseMain = seriesData[anchorIndex]?.c ?? 0;
|
|
4462
|
+
const usePercent = (state.options.scale ?? "percent") !== "price";
|
|
4463
|
+
const toPrice = usePercent && baseCompare > 0 ? (value) => baseMain * value / baseCompare : (value) => value;
|
|
4464
|
+
let lastNativeValue = Number.NaN;
|
|
4465
|
+
for (let index = endIndex; index >= startIndex; index -= 1) {
|
|
4466
|
+
const value = aligned[index];
|
|
4467
|
+
if (value !== void 0 && Number.isFinite(value)) {
|
|
4468
|
+
lastNativeValue = value;
|
|
4469
|
+
break;
|
|
4470
|
+
}
|
|
4471
|
+
}
|
|
4472
|
+
comparePlans.push({ state, aligned, toPrice, usePercent, baseCompare, lastNativeValue });
|
|
4473
|
+
}
|
|
4386
4474
|
const skipLatestIndex = mergedOptions.autoScaleIgnoreLatestCandle && data.length > 1 ? data.length - 1 : -1;
|
|
4387
4475
|
const scanCandleRange = (from, to) => {
|
|
4388
4476
|
let min = Number.POSITIVE_INFINITY;
|
|
@@ -4428,6 +4516,23 @@ function createChart(element, options = {}) {
|
|
|
4428
4516
|
maxPrice = Math.max(maxPrice, weightedMax);
|
|
4429
4517
|
}
|
|
4430
4518
|
}
|
|
4519
|
+
for (const plan of comparePlans) {
|
|
4520
|
+
if (plan.state.options.includeInAutoScale === false) continue;
|
|
4521
|
+
let compareMin = Number.POSITIVE_INFINITY;
|
|
4522
|
+
let compareMax = Number.NEGATIVE_INFINITY;
|
|
4523
|
+
for (let index = startIndex; index <= endIndex; index += 1) {
|
|
4524
|
+
const value = plan.aligned[index];
|
|
4525
|
+
if (value === void 0 || !Number.isFinite(value)) continue;
|
|
4526
|
+
const price = plan.toPrice(value);
|
|
4527
|
+
if (!Number.isFinite(price)) continue;
|
|
4528
|
+
if (price < compareMin) compareMin = price;
|
|
4529
|
+
if (price > compareMax) compareMax = price;
|
|
4530
|
+
}
|
|
4531
|
+
if (compareMin <= compareMax) {
|
|
4532
|
+
minPrice = Math.min(minPrice, compareMin);
|
|
4533
|
+
maxPrice = Math.max(maxPrice, compareMax);
|
|
4534
|
+
}
|
|
4535
|
+
}
|
|
4431
4536
|
const priceRange = maxPrice - minPrice || 1;
|
|
4432
4537
|
const autoMin = minPrice - priceRange * 0.08;
|
|
4433
4538
|
const autoMax = maxPrice + priceRange * 0.08;
|
|
@@ -5539,7 +5644,106 @@ function createChart(element, options = {}) {
|
|
|
5539
5644
|
}
|
|
5540
5645
|
return data[index]?.v;
|
|
5541
5646
|
};
|
|
5542
|
-
|
|
5647
|
+
const downsamplingOptions = mergedOptions.downsampling ?? DEFAULT_OPTIONS.downsampling;
|
|
5648
|
+
const downsampleThresholdPx = Math.max(0.2, downsamplingOptions?.thresholdPx ?? 1.5);
|
|
5649
|
+
const shouldDownsample = (downsamplingOptions?.enabled ?? true) && candleSpacing < downsampleThresholdPx && endIndex - startIndex > 2;
|
|
5650
|
+
const seriesColumns = [];
|
|
5651
|
+
if (shouldDownsample) {
|
|
5652
|
+
let current = null;
|
|
5653
|
+
let currentColumn = Number.NaN;
|
|
5654
|
+
for (let index = startIndex; index <= endIndex; index += 1) {
|
|
5655
|
+
const point = seriesData[index];
|
|
5656
|
+
if (!point) continue;
|
|
5657
|
+
const column = Math.round(chartLeft + (index + 0.5 - xStart) / xSpan * chartWidth);
|
|
5658
|
+
if (!current || column !== currentColumn) {
|
|
5659
|
+
currentColumn = column;
|
|
5660
|
+
current = {
|
|
5661
|
+
x: column,
|
|
5662
|
+
open: point.o,
|
|
5663
|
+
high: point.h,
|
|
5664
|
+
low: point.l,
|
|
5665
|
+
close: point.c,
|
|
5666
|
+
minClose: point.c,
|
|
5667
|
+
maxClose: point.c,
|
|
5668
|
+
minCloseIndex: index,
|
|
5669
|
+
maxCloseIndex: index
|
|
5670
|
+
};
|
|
5671
|
+
seriesColumns.push(current);
|
|
5672
|
+
continue;
|
|
5673
|
+
}
|
|
5674
|
+
if (point.h > current.high) current.high = point.h;
|
|
5675
|
+
if (point.l < current.low) current.low = point.l;
|
|
5676
|
+
current.close = point.c;
|
|
5677
|
+
if (point.c < current.minClose) {
|
|
5678
|
+
current.minClose = point.c;
|
|
5679
|
+
current.minCloseIndex = index;
|
|
5680
|
+
}
|
|
5681
|
+
if (point.c > current.maxClose) {
|
|
5682
|
+
current.maxClose = point.c;
|
|
5683
|
+
current.maxCloseIndex = index;
|
|
5684
|
+
}
|
|
5685
|
+
}
|
|
5686
|
+
}
|
|
5687
|
+
if (shouldDownsample && !isLineStyleChart) {
|
|
5688
|
+
const upColumns = new Path2D();
|
|
5689
|
+
const downColumns = new Path2D();
|
|
5690
|
+
for (const column of seriesColumns) {
|
|
5691
|
+
const path = column.close >= column.open ? upColumns : downColumns;
|
|
5692
|
+
const x = column.x + 0.5;
|
|
5693
|
+
const top = crisp(yFromPrice(column.high));
|
|
5694
|
+
const bottom = crisp(yFromPrice(column.low));
|
|
5695
|
+
path.moveTo(x, top);
|
|
5696
|
+
path.lineTo(x, bottom === top ? bottom + 1 : bottom);
|
|
5697
|
+
}
|
|
5698
|
+
ctx.lineWidth = 1;
|
|
5699
|
+
ctx.strokeStyle = mergedOptions.upColor;
|
|
5700
|
+
ctx.stroke(upColumns);
|
|
5701
|
+
ctx.strokeStyle = mergedOptions.downColor;
|
|
5702
|
+
ctx.stroke(downColumns);
|
|
5703
|
+
} else if (shouldDownsample && isLineStyleChart) {
|
|
5704
|
+
const seriesLineWidth = Math.max(1, mergedOptions.lineWidth);
|
|
5705
|
+
const linePath = new Path2D();
|
|
5706
|
+
let started = false;
|
|
5707
|
+
let firstX = 0;
|
|
5708
|
+
let lastX = 0;
|
|
5709
|
+
for (const column of seriesColumns) {
|
|
5710
|
+
const lowFirst = column.minCloseIndex <= column.maxCloseIndex;
|
|
5711
|
+
const first = lowFirst ? column.minClose : column.maxClose;
|
|
5712
|
+
const second = lowFirst ? column.maxClose : column.minClose;
|
|
5713
|
+
const x = column.x;
|
|
5714
|
+
if (!started) {
|
|
5715
|
+
linePath.moveTo(x, yFromPrice(first));
|
|
5716
|
+
firstX = x;
|
|
5717
|
+
started = true;
|
|
5718
|
+
} else {
|
|
5719
|
+
linePath.lineTo(x, yFromPrice(first));
|
|
5720
|
+
}
|
|
5721
|
+
if (second !== first) {
|
|
5722
|
+
linePath.lineTo(x, yFromPrice(second));
|
|
5723
|
+
}
|
|
5724
|
+
lastX = x;
|
|
5725
|
+
}
|
|
5726
|
+
if (started) {
|
|
5727
|
+
if (chartType === "area" || chartType === "baseline") {
|
|
5728
|
+
const baseY = chartType === "baseline" ? yFromPrice(mergedOptions.baselinePrice ?? (yMin + yMax) / 2) : chartBottom;
|
|
5729
|
+
const fillPath = new Path2D(linePath);
|
|
5730
|
+
fillPath.lineTo(lastX, baseY);
|
|
5731
|
+
fillPath.lineTo(firstX, baseY);
|
|
5732
|
+
fillPath.closePath();
|
|
5733
|
+
ctx.save();
|
|
5734
|
+
ctx.globalAlpha = Math.max(0, Math.min(1, mergedOptions.areaFillOpacity));
|
|
5735
|
+
ctx.fillStyle = chartType === "baseline" ? mergedOptions.upColor : mergedOptions.lineColor;
|
|
5736
|
+
ctx.fill(fillPath);
|
|
5737
|
+
ctx.restore();
|
|
5738
|
+
}
|
|
5739
|
+
ctx.save();
|
|
5740
|
+
ctx.strokeStyle = chartType === "baseline" ? mergedOptions.upColor : mergedOptions.lineColor;
|
|
5741
|
+
ctx.lineWidth = seriesLineWidth;
|
|
5742
|
+
ctx.lineJoin = "round";
|
|
5743
|
+
ctx.stroke(linePath);
|
|
5744
|
+
ctx.restore();
|
|
5745
|
+
}
|
|
5746
|
+
} else if (isLineStyleChart) {
|
|
5543
5747
|
const seriesLineWidth = Math.max(1, mergedOptions.lineWidth);
|
|
5544
5748
|
const linePath = new Path2D();
|
|
5545
5749
|
let started = false;
|
|
@@ -5705,6 +5909,105 @@ function createChart(element, options = {}) {
|
|
|
5705
5909
|
ctx.stroke(downHollowBodies);
|
|
5706
5910
|
}
|
|
5707
5911
|
}
|
|
5912
|
+
const compareAxisTags = [];
|
|
5913
|
+
for (const plan of comparePlans) {
|
|
5914
|
+
const color = plan.state.options.color ?? "#f59e0b";
|
|
5915
|
+
const lineWidth = Math.max(1, plan.state.options.lineWidth ?? 1.5);
|
|
5916
|
+
const path = new Path2D();
|
|
5917
|
+
let started = false;
|
|
5918
|
+
let firstX = 0;
|
|
5919
|
+
let lastX = 0;
|
|
5920
|
+
let lastY = 0;
|
|
5921
|
+
let lastPrice = Number.NaN;
|
|
5922
|
+
const emit = (x, y) => {
|
|
5923
|
+
if (!started) {
|
|
5924
|
+
path.moveTo(x, y);
|
|
5925
|
+
firstX = x;
|
|
5926
|
+
started = true;
|
|
5927
|
+
} else {
|
|
5928
|
+
path.lineTo(x, y);
|
|
5929
|
+
}
|
|
5930
|
+
lastX = x;
|
|
5931
|
+
lastY = y;
|
|
5932
|
+
};
|
|
5933
|
+
const xForIndex = (index) => chartLeft + (index + 0.5 - xStart) / xSpan * chartWidth;
|
|
5934
|
+
if (shouldDownsample) {
|
|
5935
|
+
let column = Number.NaN;
|
|
5936
|
+
let minValue = 0;
|
|
5937
|
+
let maxValue = 0;
|
|
5938
|
+
let minIndex = 0;
|
|
5939
|
+
let maxIndex = 0;
|
|
5940
|
+
let hasColumn = false;
|
|
5941
|
+
const flushColumn = () => {
|
|
5942
|
+
if (!hasColumn) return;
|
|
5943
|
+
const lowFirst = minIndex <= maxIndex;
|
|
5944
|
+
const first = plan.toPrice(lowFirst ? minValue : maxValue);
|
|
5945
|
+
const second = plan.toPrice(lowFirst ? maxValue : minValue);
|
|
5946
|
+
if (Number.isFinite(first)) emit(column, yFromPrice(first));
|
|
5947
|
+
if (Number.isFinite(second) && second !== first) emit(column, yFromPrice(second));
|
|
5948
|
+
lastPrice = plan.toPrice(maxIndex >= minIndex ? maxValue : minValue);
|
|
5949
|
+
};
|
|
5950
|
+
for (let index = startIndex; index <= endIndex; index += 1) {
|
|
5951
|
+
const value = plan.aligned[index];
|
|
5952
|
+
if (value === void 0 || !Number.isFinite(value)) continue;
|
|
5953
|
+
const nextColumn = Math.round(xForIndex(index));
|
|
5954
|
+
if (!hasColumn || nextColumn !== column) {
|
|
5955
|
+
flushColumn();
|
|
5956
|
+
column = nextColumn;
|
|
5957
|
+
minValue = value;
|
|
5958
|
+
maxValue = value;
|
|
5959
|
+
minIndex = index;
|
|
5960
|
+
maxIndex = index;
|
|
5961
|
+
hasColumn = true;
|
|
5962
|
+
continue;
|
|
5963
|
+
}
|
|
5964
|
+
if (value < minValue) {
|
|
5965
|
+
minValue = value;
|
|
5966
|
+
minIndex = index;
|
|
5967
|
+
}
|
|
5968
|
+
if (value > maxValue) {
|
|
5969
|
+
maxValue = value;
|
|
5970
|
+
maxIndex = index;
|
|
5971
|
+
}
|
|
5972
|
+
}
|
|
5973
|
+
flushColumn();
|
|
5974
|
+
} else {
|
|
5975
|
+
for (let index = startIndex; index <= endIndex; index += 1) {
|
|
5976
|
+
const value = plan.aligned[index];
|
|
5977
|
+
if (value === void 0 || !Number.isFinite(value)) continue;
|
|
5978
|
+
const price = plan.toPrice(value);
|
|
5979
|
+
if (!Number.isFinite(price)) continue;
|
|
5980
|
+
lastPrice = price;
|
|
5981
|
+
emit(xForIndex(index), yFromPrice(price));
|
|
5982
|
+
}
|
|
5983
|
+
}
|
|
5984
|
+
if (!started) continue;
|
|
5985
|
+
if (plan.state.options.style === "area") {
|
|
5986
|
+
const fillPath = new Path2D(path);
|
|
5987
|
+
fillPath.lineTo(lastX, chartBottom);
|
|
5988
|
+
fillPath.lineTo(firstX, chartBottom);
|
|
5989
|
+
fillPath.closePath();
|
|
5990
|
+
ctx.save();
|
|
5991
|
+
ctx.globalAlpha = Math.max(0, Math.min(1, mergedOptions.areaFillOpacity)) * 0.6;
|
|
5992
|
+
ctx.fillStyle = color;
|
|
5993
|
+
ctx.fill(fillPath);
|
|
5994
|
+
ctx.restore();
|
|
5995
|
+
}
|
|
5996
|
+
ctx.save();
|
|
5997
|
+
ctx.strokeStyle = color;
|
|
5998
|
+
ctx.lineWidth = lineWidth;
|
|
5999
|
+
ctx.lineJoin = "round";
|
|
6000
|
+
ctx.stroke(path);
|
|
6001
|
+
ctx.restore();
|
|
6002
|
+
if (Number.isFinite(lastPrice)) {
|
|
6003
|
+
const label = plan.state.options.label ?? plan.state.options.id;
|
|
6004
|
+
const nativeLast = plan.lastNativeValue;
|
|
6005
|
+
const usePercentTag = plan.usePercent && plan.baseCompare > 0 && Number.isFinite(nativeLast);
|
|
6006
|
+
const changePct = usePercentTag ? (nativeLast / plan.baseCompare - 1) * 100 : 0;
|
|
6007
|
+
const shortText = usePercentTag ? `${changePct >= 0 ? "+" : ""}${changePct.toFixed(2)}%` : formatPrice(nativeLast);
|
|
6008
|
+
compareAxisTags.push({ y: lastY, text: `${label} ${shortText}`, shortText, color });
|
|
6009
|
+
}
|
|
6010
|
+
}
|
|
5708
6011
|
const activeOverlayIndicators = indicators.filter((indicator) => indicator.visible).map((indicator) => ({ indicator, plugin: indicatorRegistry.get(indicator.type) })).filter(
|
|
5709
6012
|
(value) => value.plugin !== void 0 && (value.indicator.pane ?? value.plugin.pane ?? "overlay") === "overlay"
|
|
5710
6013
|
).sort((a, b) => a.indicator.zIndex - b.indicator.zIndex);
|
|
@@ -6479,6 +6782,35 @@ function createChart(element, options = {}) {
|
|
|
6479
6782
|
drawText(countdownText, chartRight + 6, (fullChartBottom + height) / 2, "left", "middle", xAxis.textColor);
|
|
6480
6783
|
ctx.font = prevFont;
|
|
6481
6784
|
}
|
|
6785
|
+
if (compareAxisTags.length > 0 && width - chartRight > 0) {
|
|
6786
|
+
const prevFont = ctx.font;
|
|
6787
|
+
const tagFontSize = Math.max(9, (resolvedAxis.fontSize ?? 12) - 2);
|
|
6788
|
+
ctx.font = `${tagFontSize}px ${mergedOptions.fontFamily}`;
|
|
6789
|
+
ctx.textBaseline = "middle";
|
|
6790
|
+
ctx.textAlign = "left";
|
|
6791
|
+
const placed = [];
|
|
6792
|
+
const tagHeight = tagFontSize + 6;
|
|
6793
|
+
const gutterWidth = width - chartRight - 4;
|
|
6794
|
+
for (const tag of [...compareAxisTags].sort((a, b) => a.y - b.y)) {
|
|
6795
|
+
const text = measureTextWidth(tag.text) + 10 <= gutterWidth ? tag.text : tag.shortText;
|
|
6796
|
+
const tagWidth = Math.min(gutterWidth, measureTextWidth(text) + 10);
|
|
6797
|
+
if (tagWidth <= 0) continue;
|
|
6798
|
+
let tagY = clamp(tag.y - tagHeight / 2, chartTop, chartBottom - tagHeight);
|
|
6799
|
+
for (const slot of placed) {
|
|
6800
|
+
if (tagY < slot.bottom + 2 && tagY + tagHeight > slot.top - 2) {
|
|
6801
|
+
tagY = slot.bottom + 2;
|
|
6802
|
+
}
|
|
6803
|
+
}
|
|
6804
|
+
placed.push({ top: tagY, bottom: tagY + tagHeight });
|
|
6805
|
+
ctx.save();
|
|
6806
|
+
ctx.fillStyle = tag.color;
|
|
6807
|
+
fillRoundedRect(chartRight + 2, Math.round(tagY), Math.round(tagWidth), tagHeight, 3);
|
|
6808
|
+
ctx.fillStyle = labelTextColorOn(tag.color, "#ffffff");
|
|
6809
|
+
ctx.fillText(text, chartRight + 7, Math.round(tagY + tagHeight / 2));
|
|
6810
|
+
ctx.restore();
|
|
6811
|
+
}
|
|
6812
|
+
ctx.font = prevFont;
|
|
6813
|
+
}
|
|
6482
6814
|
if (baseCtx) {
|
|
6483
6815
|
if (baseCanvas.width !== canvas.width || baseCanvas.height !== canvas.height) {
|
|
6484
6816
|
baseCanvas.width = canvas.width;
|
|
@@ -9580,6 +9912,10 @@ function createChart(element, options = {}) {
|
|
|
9580
9912
|
getMagnetMode,
|
|
9581
9913
|
setPriceScale,
|
|
9582
9914
|
getPriceScale,
|
|
9915
|
+
setCompareSeries,
|
|
9916
|
+
addCompareSeries,
|
|
9917
|
+
removeCompareSeries,
|
|
9918
|
+
getCompareSeries,
|
|
9583
9919
|
cancelDrawing,
|
|
9584
9920
|
setTradeMarkers,
|
|
9585
9921
|
setSelectedDrawing,
|
|
@@ -80,6 +80,7 @@ interface ChartOptions {
|
|
|
80
80
|
doubleClickAction?: "reset" | "placeLimitOrder";
|
|
81
81
|
keyboard?: KeyboardOptions;
|
|
82
82
|
accessibility?: AccessibilityOptions;
|
|
83
|
+
downsampling?: DownsamplingOptions;
|
|
83
84
|
crosshair?: CrosshairOptions;
|
|
84
85
|
grid?: GridOptions;
|
|
85
86
|
watermark?: WatermarkOptions;
|
|
@@ -552,6 +553,45 @@ interface KeyboardOptions {
|
|
|
552
553
|
/** Delete/Backspace removes the selected drawing. Default true. */
|
|
553
554
|
deleteSelectedDrawing?: boolean;
|
|
554
555
|
}
|
|
556
|
+
/**
|
|
557
|
+
* Deep zoom-out rendering. Once bars are narrower than `thresholdPx`, several
|
|
558
|
+
* of them share a pixel column and drawing each one is wasted work, so the
|
|
559
|
+
* chart aggregates every column into one min/max bucket: path operations then
|
|
560
|
+
* scale with the chart's width instead of the number of bars. Extremes are
|
|
561
|
+
* preserved exactly (a spike never disappears), which is why this uses
|
|
562
|
+
* min/max rather than sampling.
|
|
563
|
+
*/
|
|
564
|
+
interface DownsamplingOptions {
|
|
565
|
+
/** Default true. */
|
|
566
|
+
enabled?: boolean;
|
|
567
|
+
/** Bar width in pixels below which columns are aggregated. Default 1.5. */
|
|
568
|
+
thresholdPx?: number;
|
|
569
|
+
}
|
|
570
|
+
/**
|
|
571
|
+
* An extra instrument overlaid on the main pane ("compare" in TradingView).
|
|
572
|
+
* The host owns the data and keeps it fresh; the chart aligns it to the main
|
|
573
|
+
* series by timestamp and draws it.
|
|
574
|
+
*/
|
|
575
|
+
interface CompareSeriesOptions {
|
|
576
|
+
id: string;
|
|
577
|
+
/** Shown on the right-axis tag, e.g. "NQ1!". */
|
|
578
|
+
label?: string;
|
|
579
|
+
/** Bars for the compared instrument; aligned to the main series by time. */
|
|
580
|
+
data: OhlcDataPoint[];
|
|
581
|
+
color?: string;
|
|
582
|
+
lineWidth?: number;
|
|
583
|
+
style?: "line" | "area";
|
|
584
|
+
/**
|
|
585
|
+
* "percent" (default) normalises both instruments to their first visible
|
|
586
|
+
* bar so shapes can be compared regardless of nominal price — the same
|
|
587
|
+
* thing TradingView does when you add a comparison. "price" plots the raw
|
|
588
|
+
* values on the main scale, which only makes sense for related instruments.
|
|
589
|
+
*/
|
|
590
|
+
scale?: "percent" | "price";
|
|
591
|
+
visible?: boolean;
|
|
592
|
+
/** Let this series widen the price scale. Default true. */
|
|
593
|
+
includeInAutoScale?: boolean;
|
|
594
|
+
}
|
|
555
595
|
interface AccessibilityOptions {
|
|
556
596
|
/** aria-label on the canvas. Default "Price chart". */
|
|
557
597
|
label?: string;
|
|
@@ -708,6 +748,17 @@ interface ChartInstance {
|
|
|
708
748
|
*/
|
|
709
749
|
setPriceScale: (options: Partial<PriceScaleOptions>) => void;
|
|
710
750
|
getPriceScale: () => PriceScaleOptions;
|
|
751
|
+
/**
|
|
752
|
+
* Overlay other instruments on the main pane for comparison. Series are
|
|
753
|
+
* aligned to the main series by timestamp (last known value carries forward
|
|
754
|
+
* across gaps) and, by default, normalised to percent change from the first
|
|
755
|
+
* visible bar so differently-priced instruments stay comparable. Data is not
|
|
756
|
+
* part of `saveState()` — the host re-supplies it.
|
|
757
|
+
*/
|
|
758
|
+
setCompareSeries: (series: CompareSeriesOptions[]) => void;
|
|
759
|
+
addCompareSeries: (series: CompareSeriesOptions) => void;
|
|
760
|
+
removeCompareSeries: (id: string) => void;
|
|
761
|
+
getCompareSeries: () => CompareSeriesOptions[];
|
|
711
762
|
cancelDrawing: () => boolean;
|
|
712
763
|
setTradeMarkers: (markers: TradeMarkerOptions[]) => void;
|
|
713
764
|
onDrawingHover: (handler: ((event: DrawingHoverEvent) => void) | null) => void;
|
|
@@ -897,4 +948,4 @@ declare const compileScriptIndicator: (definition: ScriptIndicatorDefinition) =>
|
|
|
897
948
|
|
|
898
949
|
declare function createChart(element: HTMLElement, options?: ChartOptions): ChartInstance;
|
|
899
950
|
|
|
900
|
-
export { type AccessibilityOptions, type AxisOptions, type BuiltInIndicatorInfo, type ChartClickEvent, type ChartContextMenuEvent, type ChartContextMenuRegion, type ChartInstance, type ChartKeyboardAction, type ChartKeyboardShortcutEvent, type ChartOptions, type ChartSavedState, type ChartType, type CrosshairMoveEvent, type CrosshairOptions, type CrosshairPriceActionEvent, type DashPatternOptions, type DrawingBounds, type DrawingDefaults, type DrawingHoverEvent, type DrawingObjectOptions, type DrawingPoint, type DrawingSelectEvent, type DrawingSelectionChangeEvent, type DrawingToolType, FIB_DEFAULT_PALETTE, type GridOptions, type IndicatorInstanceOptions, type IndicatorPane, type IndicatorPaneActionEvent, type IndicatorPaneAxisOptions, type IndicatorPaneGuideLine, type IndicatorPaneHeightChangeEvent, type IndicatorPaneRenderInfo, type IndicatorPaneValue, type IndicatorPaneValueLabel, type IndicatorPlugin, type IndicatorRenderContext, type KeyboardOptions, type LabelsOptions, type OhlcDataPoint, type OrderActionButton, type OrderActionEvent, type OrderLineOptions, POSITION_DEFAULT_COLORS, type PriceLineOptions, type PriceScaleMode, type PriceScaleOptions, SCRIPT_SOURCE_INPUT_VALUES, type ScriptComputeResult, type ScriptIndicatorDefinition, type ScriptIndicatorInputDef, type ScriptInputHelper, type ScriptInputOptions, type ScriptPlotSpec, type TickerLineOptions, type TradeMarkerOptions, type ViewportState, type WatermarkOptions, compileScriptIndicator, createChart, extractScriptInputs, validateScriptSource };
|
|
951
|
+
export { type AccessibilityOptions, type AxisOptions, type BuiltInIndicatorInfo, type ChartClickEvent, type ChartContextMenuEvent, type ChartContextMenuRegion, type ChartInstance, type ChartKeyboardAction, type ChartKeyboardShortcutEvent, type ChartOptions, type ChartSavedState, type ChartType, type CompareSeriesOptions, type CrosshairMoveEvent, type CrosshairOptions, type CrosshairPriceActionEvent, type DashPatternOptions, type DownsamplingOptions, type DrawingBounds, type DrawingDefaults, type DrawingHoverEvent, type DrawingObjectOptions, type DrawingPoint, type DrawingSelectEvent, type DrawingSelectionChangeEvent, type DrawingToolType, FIB_DEFAULT_PALETTE, type GridOptions, type IndicatorInstanceOptions, type IndicatorPane, type IndicatorPaneActionEvent, type IndicatorPaneAxisOptions, type IndicatorPaneGuideLine, type IndicatorPaneHeightChangeEvent, type IndicatorPaneRenderInfo, type IndicatorPaneValue, type IndicatorPaneValueLabel, type IndicatorPlugin, type IndicatorRenderContext, type KeyboardOptions, type LabelsOptions, type OhlcDataPoint, type OrderActionButton, type OrderActionEvent, type OrderLineOptions, POSITION_DEFAULT_COLORS, type PriceLineOptions, type PriceScaleMode, type PriceScaleOptions, SCRIPT_SOURCE_INPUT_VALUES, type ScriptComputeResult, type ScriptIndicatorDefinition, type ScriptIndicatorInputDef, type ScriptInputHelper, type ScriptInputOptions, type ScriptPlotSpec, type TickerLineOptions, type TradeMarkerOptions, type ViewportState, type WatermarkOptions, compileScriptIndicator, createChart, extractScriptInputs, validateScriptSource };
|