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
|
@@ -2821,6 +2821,7 @@ var DEFAULT_OPTIONS = {
|
|
|
2821
2821
|
doubleClickAction: "reset",
|
|
2822
2822
|
keyboard: { enabled: true, panBars: 1, deleteSelectedDrawing: true },
|
|
2823
2823
|
accessibility: { label: "Price chart", description: "", focusable: true },
|
|
2824
|
+
downsampling: { enabled: true, thresholdPx: 1.5 },
|
|
2824
2825
|
crosshair: DEFAULT_CROSSHAIR_OPTIONS,
|
|
2825
2826
|
grid: DEFAULT_GRID_OPTIONS,
|
|
2826
2827
|
watermark: DEFAULT_WATERMARK_OPTIONS,
|
|
@@ -2871,6 +2872,10 @@ var mergeChartOptions = (baseOptions, options = {}) => ({
|
|
|
2871
2872
|
...baseOptions.accessibility,
|
|
2872
2873
|
...options.accessibility ?? {}
|
|
2873
2874
|
},
|
|
2875
|
+
downsampling: {
|
|
2876
|
+
...baseOptions.downsampling,
|
|
2877
|
+
...options.downsampling ?? {}
|
|
2878
|
+
},
|
|
2874
2879
|
crosshair: {
|
|
2875
2880
|
...baseOptions.crosshair,
|
|
2876
2881
|
...options.crosshair ?? {}
|
|
@@ -3746,6 +3751,64 @@ function createChart(element, options = {}) {
|
|
|
3746
3751
|
heikinAshiFingerprint = fingerprint;
|
|
3747
3752
|
return result;
|
|
3748
3753
|
};
|
|
3754
|
+
let compareSeriesStates = [];
|
|
3755
|
+
const prepareCompareSeries = (options2) => {
|
|
3756
|
+
const rows = [];
|
|
3757
|
+
for (const point of options2.data ?? []) {
|
|
3758
|
+
const t = new Date(point.t).getTime();
|
|
3759
|
+
const c = Number(point.c);
|
|
3760
|
+
if (Number.isFinite(t) && Number.isFinite(c)) rows.push({ t, c });
|
|
3761
|
+
}
|
|
3762
|
+
rows.sort((a, b) => a.t - b.t);
|
|
3763
|
+
return {
|
|
3764
|
+
options: options2,
|
|
3765
|
+
times: rows.map((row) => row.t),
|
|
3766
|
+
closes: rows.map((row) => row.c),
|
|
3767
|
+
aligned: null,
|
|
3768
|
+
alignedFingerprint: ""
|
|
3769
|
+
};
|
|
3770
|
+
};
|
|
3771
|
+
const mainDataFingerprint = () => {
|
|
3772
|
+
const last = data[data.length - 1];
|
|
3773
|
+
return last ? `${data.length}|${data[0].time.getTime()}|${last.time.getTime()}` : "empty";
|
|
3774
|
+
};
|
|
3775
|
+
const getAlignedCompareValues = (state) => {
|
|
3776
|
+
const fingerprint = `${mainDataFingerprint()}|${state.times.length}|${state.times[state.times.length - 1] ?? 0}`;
|
|
3777
|
+
if (state.aligned && state.alignedFingerprint === fingerprint) {
|
|
3778
|
+
return state.aligned;
|
|
3779
|
+
}
|
|
3780
|
+
const aligned = new Float64Array(data.length).fill(Number.NaN);
|
|
3781
|
+
let cursor = 0;
|
|
3782
|
+
let lastValue = Number.NaN;
|
|
3783
|
+
for (let i = 0; i < data.length; i += 1) {
|
|
3784
|
+
const barTime = data[i].time.getTime();
|
|
3785
|
+
while (cursor < state.times.length && state.times[cursor] <= barTime) {
|
|
3786
|
+
lastValue = state.closes[cursor];
|
|
3787
|
+
cursor += 1;
|
|
3788
|
+
}
|
|
3789
|
+
aligned[i] = lastValue;
|
|
3790
|
+
}
|
|
3791
|
+
state.aligned = aligned;
|
|
3792
|
+
state.alignedFingerprint = fingerprint;
|
|
3793
|
+
return aligned;
|
|
3794
|
+
};
|
|
3795
|
+
const visibleCompareSeries = () => compareSeriesStates.filter((state) => state.options.visible !== false && state.times.length > 0);
|
|
3796
|
+
const setCompareSeries = (series) => {
|
|
3797
|
+
compareSeriesStates = (series ?? []).filter((entry) => entry && entry.id).map(prepareCompareSeries);
|
|
3798
|
+
scheduleDraw({ updateAutoScale: true });
|
|
3799
|
+
};
|
|
3800
|
+
const addCompareSeries = (series) => {
|
|
3801
|
+
if (!series?.id) return;
|
|
3802
|
+
compareSeriesStates = [...compareSeriesStates.filter((state) => state.options.id !== series.id), prepareCompareSeries(series)];
|
|
3803
|
+
scheduleDraw({ updateAutoScale: true });
|
|
3804
|
+
};
|
|
3805
|
+
const removeCompareSeries = (id) => {
|
|
3806
|
+
const next = compareSeriesStates.filter((state) => state.options.id !== id);
|
|
3807
|
+
if (next.length === compareSeriesStates.length) return;
|
|
3808
|
+
compareSeriesStates = next;
|
|
3809
|
+
scheduleDraw({ updateAutoScale: true });
|
|
3810
|
+
};
|
|
3811
|
+
const getCompareSeries = () => compareSeriesStates.map((state) => state.options);
|
|
3749
3812
|
const formatHoverTimeLabel = (time, mode) => {
|
|
3750
3813
|
if (mode === "time") {
|
|
3751
3814
|
return time.toLocaleTimeString(void 0, {
|
|
@@ -4351,6 +4414,31 @@ function createChart(element, options = {}) {
|
|
|
4351
4414
|
const chartType = mergedOptions.chartType;
|
|
4352
4415
|
const seriesData = chartType === "heikin-ashi" ? getHeikinAshiData() : data;
|
|
4353
4416
|
const isLineStyleChart = chartType === "line" || chartType === "area" || chartType === "baseline";
|
|
4417
|
+
const comparePlans = [];
|
|
4418
|
+
for (const state of visibleCompareSeries()) {
|
|
4419
|
+
const aligned = getAlignedCompareValues(state);
|
|
4420
|
+
let anchorIndex = -1;
|
|
4421
|
+
for (let index = startIndex; index <= endIndex; index += 1) {
|
|
4422
|
+
if (Number.isFinite(aligned[index] ?? Number.NaN)) {
|
|
4423
|
+
anchorIndex = index;
|
|
4424
|
+
break;
|
|
4425
|
+
}
|
|
4426
|
+
}
|
|
4427
|
+
if (anchorIndex < 0) continue;
|
|
4428
|
+
const baseCompare = aligned[anchorIndex];
|
|
4429
|
+
const baseMain = seriesData[anchorIndex]?.c ?? 0;
|
|
4430
|
+
const usePercent = (state.options.scale ?? "percent") !== "price";
|
|
4431
|
+
const toPrice = usePercent && baseCompare > 0 ? (value) => baseMain * value / baseCompare : (value) => value;
|
|
4432
|
+
let lastNativeValue = Number.NaN;
|
|
4433
|
+
for (let index = endIndex; index >= startIndex; index -= 1) {
|
|
4434
|
+
const value = aligned[index];
|
|
4435
|
+
if (value !== void 0 && Number.isFinite(value)) {
|
|
4436
|
+
lastNativeValue = value;
|
|
4437
|
+
break;
|
|
4438
|
+
}
|
|
4439
|
+
}
|
|
4440
|
+
comparePlans.push({ state, aligned, toPrice, usePercent, baseCompare, lastNativeValue });
|
|
4441
|
+
}
|
|
4354
4442
|
const skipLatestIndex = mergedOptions.autoScaleIgnoreLatestCandle && data.length > 1 ? data.length - 1 : -1;
|
|
4355
4443
|
const scanCandleRange = (from, to) => {
|
|
4356
4444
|
let min = Number.POSITIVE_INFINITY;
|
|
@@ -4396,6 +4484,23 @@ function createChart(element, options = {}) {
|
|
|
4396
4484
|
maxPrice = Math.max(maxPrice, weightedMax);
|
|
4397
4485
|
}
|
|
4398
4486
|
}
|
|
4487
|
+
for (const plan of comparePlans) {
|
|
4488
|
+
if (plan.state.options.includeInAutoScale === false) continue;
|
|
4489
|
+
let compareMin = Number.POSITIVE_INFINITY;
|
|
4490
|
+
let compareMax = Number.NEGATIVE_INFINITY;
|
|
4491
|
+
for (let index = startIndex; index <= endIndex; index += 1) {
|
|
4492
|
+
const value = plan.aligned[index];
|
|
4493
|
+
if (value === void 0 || !Number.isFinite(value)) continue;
|
|
4494
|
+
const price = plan.toPrice(value);
|
|
4495
|
+
if (!Number.isFinite(price)) continue;
|
|
4496
|
+
if (price < compareMin) compareMin = price;
|
|
4497
|
+
if (price > compareMax) compareMax = price;
|
|
4498
|
+
}
|
|
4499
|
+
if (compareMin <= compareMax) {
|
|
4500
|
+
minPrice = Math.min(minPrice, compareMin);
|
|
4501
|
+
maxPrice = Math.max(maxPrice, compareMax);
|
|
4502
|
+
}
|
|
4503
|
+
}
|
|
4399
4504
|
const priceRange = maxPrice - minPrice || 1;
|
|
4400
4505
|
const autoMin = minPrice - priceRange * 0.08;
|
|
4401
4506
|
const autoMax = maxPrice + priceRange * 0.08;
|
|
@@ -5507,7 +5612,106 @@ function createChart(element, options = {}) {
|
|
|
5507
5612
|
}
|
|
5508
5613
|
return data[index]?.v;
|
|
5509
5614
|
};
|
|
5510
|
-
|
|
5615
|
+
const downsamplingOptions = mergedOptions.downsampling ?? DEFAULT_OPTIONS.downsampling;
|
|
5616
|
+
const downsampleThresholdPx = Math.max(0.2, downsamplingOptions?.thresholdPx ?? 1.5);
|
|
5617
|
+
const shouldDownsample = (downsamplingOptions?.enabled ?? true) && candleSpacing < downsampleThresholdPx && endIndex - startIndex > 2;
|
|
5618
|
+
const seriesColumns = [];
|
|
5619
|
+
if (shouldDownsample) {
|
|
5620
|
+
let current = null;
|
|
5621
|
+
let currentColumn = Number.NaN;
|
|
5622
|
+
for (let index = startIndex; index <= endIndex; index += 1) {
|
|
5623
|
+
const point = seriesData[index];
|
|
5624
|
+
if (!point) continue;
|
|
5625
|
+
const column = Math.round(chartLeft + (index + 0.5 - xStart) / xSpan * chartWidth);
|
|
5626
|
+
if (!current || column !== currentColumn) {
|
|
5627
|
+
currentColumn = column;
|
|
5628
|
+
current = {
|
|
5629
|
+
x: column,
|
|
5630
|
+
open: point.o,
|
|
5631
|
+
high: point.h,
|
|
5632
|
+
low: point.l,
|
|
5633
|
+
close: point.c,
|
|
5634
|
+
minClose: point.c,
|
|
5635
|
+
maxClose: point.c,
|
|
5636
|
+
minCloseIndex: index,
|
|
5637
|
+
maxCloseIndex: index
|
|
5638
|
+
};
|
|
5639
|
+
seriesColumns.push(current);
|
|
5640
|
+
continue;
|
|
5641
|
+
}
|
|
5642
|
+
if (point.h > current.high) current.high = point.h;
|
|
5643
|
+
if (point.l < current.low) current.low = point.l;
|
|
5644
|
+
current.close = point.c;
|
|
5645
|
+
if (point.c < current.minClose) {
|
|
5646
|
+
current.minClose = point.c;
|
|
5647
|
+
current.minCloseIndex = index;
|
|
5648
|
+
}
|
|
5649
|
+
if (point.c > current.maxClose) {
|
|
5650
|
+
current.maxClose = point.c;
|
|
5651
|
+
current.maxCloseIndex = index;
|
|
5652
|
+
}
|
|
5653
|
+
}
|
|
5654
|
+
}
|
|
5655
|
+
if (shouldDownsample && !isLineStyleChart) {
|
|
5656
|
+
const upColumns = new Path2D();
|
|
5657
|
+
const downColumns = new Path2D();
|
|
5658
|
+
for (const column of seriesColumns) {
|
|
5659
|
+
const path = column.close >= column.open ? upColumns : downColumns;
|
|
5660
|
+
const x = column.x + 0.5;
|
|
5661
|
+
const top = crisp(yFromPrice(column.high));
|
|
5662
|
+
const bottom = crisp(yFromPrice(column.low));
|
|
5663
|
+
path.moveTo(x, top);
|
|
5664
|
+
path.lineTo(x, bottom === top ? bottom + 1 : bottom);
|
|
5665
|
+
}
|
|
5666
|
+
ctx.lineWidth = 1;
|
|
5667
|
+
ctx.strokeStyle = mergedOptions.upColor;
|
|
5668
|
+
ctx.stroke(upColumns);
|
|
5669
|
+
ctx.strokeStyle = mergedOptions.downColor;
|
|
5670
|
+
ctx.stroke(downColumns);
|
|
5671
|
+
} else if (shouldDownsample && isLineStyleChart) {
|
|
5672
|
+
const seriesLineWidth = Math.max(1, mergedOptions.lineWidth);
|
|
5673
|
+
const linePath = new Path2D();
|
|
5674
|
+
let started = false;
|
|
5675
|
+
let firstX = 0;
|
|
5676
|
+
let lastX = 0;
|
|
5677
|
+
for (const column of seriesColumns) {
|
|
5678
|
+
const lowFirst = column.minCloseIndex <= column.maxCloseIndex;
|
|
5679
|
+
const first = lowFirst ? column.minClose : column.maxClose;
|
|
5680
|
+
const second = lowFirst ? column.maxClose : column.minClose;
|
|
5681
|
+
const x = column.x;
|
|
5682
|
+
if (!started) {
|
|
5683
|
+
linePath.moveTo(x, yFromPrice(first));
|
|
5684
|
+
firstX = x;
|
|
5685
|
+
started = true;
|
|
5686
|
+
} else {
|
|
5687
|
+
linePath.lineTo(x, yFromPrice(first));
|
|
5688
|
+
}
|
|
5689
|
+
if (second !== first) {
|
|
5690
|
+
linePath.lineTo(x, yFromPrice(second));
|
|
5691
|
+
}
|
|
5692
|
+
lastX = x;
|
|
5693
|
+
}
|
|
5694
|
+
if (started) {
|
|
5695
|
+
if (chartType === "area" || chartType === "baseline") {
|
|
5696
|
+
const baseY = chartType === "baseline" ? yFromPrice(mergedOptions.baselinePrice ?? (yMin + yMax) / 2) : chartBottom;
|
|
5697
|
+
const fillPath = new Path2D(linePath);
|
|
5698
|
+
fillPath.lineTo(lastX, baseY);
|
|
5699
|
+
fillPath.lineTo(firstX, baseY);
|
|
5700
|
+
fillPath.closePath();
|
|
5701
|
+
ctx.save();
|
|
5702
|
+
ctx.globalAlpha = Math.max(0, Math.min(1, mergedOptions.areaFillOpacity));
|
|
5703
|
+
ctx.fillStyle = chartType === "baseline" ? mergedOptions.upColor : mergedOptions.lineColor;
|
|
5704
|
+
ctx.fill(fillPath);
|
|
5705
|
+
ctx.restore();
|
|
5706
|
+
}
|
|
5707
|
+
ctx.save();
|
|
5708
|
+
ctx.strokeStyle = chartType === "baseline" ? mergedOptions.upColor : mergedOptions.lineColor;
|
|
5709
|
+
ctx.lineWidth = seriesLineWidth;
|
|
5710
|
+
ctx.lineJoin = "round";
|
|
5711
|
+
ctx.stroke(linePath);
|
|
5712
|
+
ctx.restore();
|
|
5713
|
+
}
|
|
5714
|
+
} else if (isLineStyleChart) {
|
|
5511
5715
|
const seriesLineWidth = Math.max(1, mergedOptions.lineWidth);
|
|
5512
5716
|
const linePath = new Path2D();
|
|
5513
5717
|
let started = false;
|
|
@@ -5673,6 +5877,105 @@ function createChart(element, options = {}) {
|
|
|
5673
5877
|
ctx.stroke(downHollowBodies);
|
|
5674
5878
|
}
|
|
5675
5879
|
}
|
|
5880
|
+
const compareAxisTags = [];
|
|
5881
|
+
for (const plan of comparePlans) {
|
|
5882
|
+
const color = plan.state.options.color ?? "#f59e0b";
|
|
5883
|
+
const lineWidth = Math.max(1, plan.state.options.lineWidth ?? 1.5);
|
|
5884
|
+
const path = new Path2D();
|
|
5885
|
+
let started = false;
|
|
5886
|
+
let firstX = 0;
|
|
5887
|
+
let lastX = 0;
|
|
5888
|
+
let lastY = 0;
|
|
5889
|
+
let lastPrice = Number.NaN;
|
|
5890
|
+
const emit = (x, y) => {
|
|
5891
|
+
if (!started) {
|
|
5892
|
+
path.moveTo(x, y);
|
|
5893
|
+
firstX = x;
|
|
5894
|
+
started = true;
|
|
5895
|
+
} else {
|
|
5896
|
+
path.lineTo(x, y);
|
|
5897
|
+
}
|
|
5898
|
+
lastX = x;
|
|
5899
|
+
lastY = y;
|
|
5900
|
+
};
|
|
5901
|
+
const xForIndex = (index) => chartLeft + (index + 0.5 - xStart) / xSpan * chartWidth;
|
|
5902
|
+
if (shouldDownsample) {
|
|
5903
|
+
let column = Number.NaN;
|
|
5904
|
+
let minValue = 0;
|
|
5905
|
+
let maxValue = 0;
|
|
5906
|
+
let minIndex = 0;
|
|
5907
|
+
let maxIndex = 0;
|
|
5908
|
+
let hasColumn = false;
|
|
5909
|
+
const flushColumn = () => {
|
|
5910
|
+
if (!hasColumn) return;
|
|
5911
|
+
const lowFirst = minIndex <= maxIndex;
|
|
5912
|
+
const first = plan.toPrice(lowFirst ? minValue : maxValue);
|
|
5913
|
+
const second = plan.toPrice(lowFirst ? maxValue : minValue);
|
|
5914
|
+
if (Number.isFinite(first)) emit(column, yFromPrice(first));
|
|
5915
|
+
if (Number.isFinite(second) && second !== first) emit(column, yFromPrice(second));
|
|
5916
|
+
lastPrice = plan.toPrice(maxIndex >= minIndex ? maxValue : minValue);
|
|
5917
|
+
};
|
|
5918
|
+
for (let index = startIndex; index <= endIndex; index += 1) {
|
|
5919
|
+
const value = plan.aligned[index];
|
|
5920
|
+
if (value === void 0 || !Number.isFinite(value)) continue;
|
|
5921
|
+
const nextColumn = Math.round(xForIndex(index));
|
|
5922
|
+
if (!hasColumn || nextColumn !== column) {
|
|
5923
|
+
flushColumn();
|
|
5924
|
+
column = nextColumn;
|
|
5925
|
+
minValue = value;
|
|
5926
|
+
maxValue = value;
|
|
5927
|
+
minIndex = index;
|
|
5928
|
+
maxIndex = index;
|
|
5929
|
+
hasColumn = true;
|
|
5930
|
+
continue;
|
|
5931
|
+
}
|
|
5932
|
+
if (value < minValue) {
|
|
5933
|
+
minValue = value;
|
|
5934
|
+
minIndex = index;
|
|
5935
|
+
}
|
|
5936
|
+
if (value > maxValue) {
|
|
5937
|
+
maxValue = value;
|
|
5938
|
+
maxIndex = index;
|
|
5939
|
+
}
|
|
5940
|
+
}
|
|
5941
|
+
flushColumn();
|
|
5942
|
+
} else {
|
|
5943
|
+
for (let index = startIndex; index <= endIndex; index += 1) {
|
|
5944
|
+
const value = plan.aligned[index];
|
|
5945
|
+
if (value === void 0 || !Number.isFinite(value)) continue;
|
|
5946
|
+
const price = plan.toPrice(value);
|
|
5947
|
+
if (!Number.isFinite(price)) continue;
|
|
5948
|
+
lastPrice = price;
|
|
5949
|
+
emit(xForIndex(index), yFromPrice(price));
|
|
5950
|
+
}
|
|
5951
|
+
}
|
|
5952
|
+
if (!started) continue;
|
|
5953
|
+
if (plan.state.options.style === "area") {
|
|
5954
|
+
const fillPath = new Path2D(path);
|
|
5955
|
+
fillPath.lineTo(lastX, chartBottom);
|
|
5956
|
+
fillPath.lineTo(firstX, chartBottom);
|
|
5957
|
+
fillPath.closePath();
|
|
5958
|
+
ctx.save();
|
|
5959
|
+
ctx.globalAlpha = Math.max(0, Math.min(1, mergedOptions.areaFillOpacity)) * 0.6;
|
|
5960
|
+
ctx.fillStyle = color;
|
|
5961
|
+
ctx.fill(fillPath);
|
|
5962
|
+
ctx.restore();
|
|
5963
|
+
}
|
|
5964
|
+
ctx.save();
|
|
5965
|
+
ctx.strokeStyle = color;
|
|
5966
|
+
ctx.lineWidth = lineWidth;
|
|
5967
|
+
ctx.lineJoin = "round";
|
|
5968
|
+
ctx.stroke(path);
|
|
5969
|
+
ctx.restore();
|
|
5970
|
+
if (Number.isFinite(lastPrice)) {
|
|
5971
|
+
const label = plan.state.options.label ?? plan.state.options.id;
|
|
5972
|
+
const nativeLast = plan.lastNativeValue;
|
|
5973
|
+
const usePercentTag = plan.usePercent && plan.baseCompare > 0 && Number.isFinite(nativeLast);
|
|
5974
|
+
const changePct = usePercentTag ? (nativeLast / plan.baseCompare - 1) * 100 : 0;
|
|
5975
|
+
const shortText = usePercentTag ? `${changePct >= 0 ? "+" : ""}${changePct.toFixed(2)}%` : formatPrice(nativeLast);
|
|
5976
|
+
compareAxisTags.push({ y: lastY, text: `${label} ${shortText}`, shortText, color });
|
|
5977
|
+
}
|
|
5978
|
+
}
|
|
5676
5979
|
const activeOverlayIndicators = indicators.filter((indicator) => indicator.visible).map((indicator) => ({ indicator, plugin: indicatorRegistry.get(indicator.type) })).filter(
|
|
5677
5980
|
(value) => value.plugin !== void 0 && (value.indicator.pane ?? value.plugin.pane ?? "overlay") === "overlay"
|
|
5678
5981
|
).sort((a, b) => a.indicator.zIndex - b.indicator.zIndex);
|
|
@@ -6447,6 +6750,35 @@ function createChart(element, options = {}) {
|
|
|
6447
6750
|
drawText(countdownText, chartRight + 6, (fullChartBottom + height) / 2, "left", "middle", xAxis.textColor);
|
|
6448
6751
|
ctx.font = prevFont;
|
|
6449
6752
|
}
|
|
6753
|
+
if (compareAxisTags.length > 0 && width - chartRight > 0) {
|
|
6754
|
+
const prevFont = ctx.font;
|
|
6755
|
+
const tagFontSize = Math.max(9, (resolvedAxis.fontSize ?? 12) - 2);
|
|
6756
|
+
ctx.font = `${tagFontSize}px ${mergedOptions.fontFamily}`;
|
|
6757
|
+
ctx.textBaseline = "middle";
|
|
6758
|
+
ctx.textAlign = "left";
|
|
6759
|
+
const placed = [];
|
|
6760
|
+
const tagHeight = tagFontSize + 6;
|
|
6761
|
+
const gutterWidth = width - chartRight - 4;
|
|
6762
|
+
for (const tag of [...compareAxisTags].sort((a, b) => a.y - b.y)) {
|
|
6763
|
+
const text = measureTextWidth(tag.text) + 10 <= gutterWidth ? tag.text : tag.shortText;
|
|
6764
|
+
const tagWidth = Math.min(gutterWidth, measureTextWidth(text) + 10);
|
|
6765
|
+
if (tagWidth <= 0) continue;
|
|
6766
|
+
let tagY = clamp(tag.y - tagHeight / 2, chartTop, chartBottom - tagHeight);
|
|
6767
|
+
for (const slot of placed) {
|
|
6768
|
+
if (tagY < slot.bottom + 2 && tagY + tagHeight > slot.top - 2) {
|
|
6769
|
+
tagY = slot.bottom + 2;
|
|
6770
|
+
}
|
|
6771
|
+
}
|
|
6772
|
+
placed.push({ top: tagY, bottom: tagY + tagHeight });
|
|
6773
|
+
ctx.save();
|
|
6774
|
+
ctx.fillStyle = tag.color;
|
|
6775
|
+
fillRoundedRect(chartRight + 2, Math.round(tagY), Math.round(tagWidth), tagHeight, 3);
|
|
6776
|
+
ctx.fillStyle = labelTextColorOn(tag.color, "#ffffff");
|
|
6777
|
+
ctx.fillText(text, chartRight + 7, Math.round(tagY + tagHeight / 2));
|
|
6778
|
+
ctx.restore();
|
|
6779
|
+
}
|
|
6780
|
+
ctx.font = prevFont;
|
|
6781
|
+
}
|
|
6450
6782
|
if (baseCtx) {
|
|
6451
6783
|
if (baseCanvas.width !== canvas.width || baseCanvas.height !== canvas.height) {
|
|
6452
6784
|
baseCanvas.width = canvas.width;
|
|
@@ -9548,6 +9880,10 @@ function createChart(element, options = {}) {
|
|
|
9548
9880
|
getMagnetMode,
|
|
9549
9881
|
setPriceScale,
|
|
9550
9882
|
getPriceScale,
|
|
9883
|
+
setCompareSeries,
|
|
9884
|
+
addCompareSeries,
|
|
9885
|
+
removeCompareSeries,
|
|
9886
|
+
getCompareSeries,
|
|
9551
9887
|
cancelDrawing,
|
|
9552
9888
|
setTradeMarkers,
|
|
9553
9889
|
setSelectedDrawing,
|