hyperprop-charting-library 0.1.137 → 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 +653 -2
- package/dist/hyperprop-charting-library.d.ts +168 -1
- package/dist/hyperprop-charting-library.js +653 -2
- package/dist/index.cjs +653 -2
- package/dist/index.d.cts +168 -1
- package/dist/index.d.ts +168 -1
- package/dist/index.js +653 -2
- package/docs/API.md +151 -0
- package/package.json +1 -1
package/dist/index.js
CHANGED
|
@@ -2819,6 +2819,9 @@ var DEFAULT_OPTIONS = {
|
|
|
2819
2819
|
pinOutOfRangeLines: false,
|
|
2820
2820
|
doubleClickEnabled: true,
|
|
2821
2821
|
doubleClickAction: "reset",
|
|
2822
|
+
keyboard: { enabled: true, panBars: 1, deleteSelectedDrawing: true },
|
|
2823
|
+
accessibility: { label: "Price chart", description: "", focusable: true },
|
|
2824
|
+
downsampling: { enabled: true, thresholdPx: 1.5 },
|
|
2822
2825
|
crosshair: DEFAULT_CROSSHAIR_OPTIONS,
|
|
2823
2826
|
grid: DEFAULT_GRID_OPTIONS,
|
|
2824
2827
|
watermark: DEFAULT_WATERMARK_OPTIONS,
|
|
@@ -2861,6 +2864,18 @@ var mergeChartOptions = (baseOptions, options = {}) => ({
|
|
|
2861
2864
|
...options.axisColor ? { lineColor: options.axisColor, textColor: options.axisColor } : {},
|
|
2862
2865
|
...options.yAxis ?? {}
|
|
2863
2866
|
},
|
|
2867
|
+
keyboard: {
|
|
2868
|
+
...baseOptions.keyboard,
|
|
2869
|
+
...options.keyboard ?? {}
|
|
2870
|
+
},
|
|
2871
|
+
accessibility: {
|
|
2872
|
+
...baseOptions.accessibility,
|
|
2873
|
+
...options.accessibility ?? {}
|
|
2874
|
+
},
|
|
2875
|
+
downsampling: {
|
|
2876
|
+
...baseOptions.downsampling,
|
|
2877
|
+
...options.downsampling ?? {}
|
|
2878
|
+
},
|
|
2864
2879
|
crosshair: {
|
|
2865
2880
|
...baseOptions.crosshair,
|
|
2866
2881
|
...options.crosshair ?? {}
|
|
@@ -3034,6 +3049,10 @@ function createChart(element, options = {}) {
|
|
|
3034
3049
|
let drawingSelectHandler = null;
|
|
3035
3050
|
let drawingDoubleClickHandler = null;
|
|
3036
3051
|
let drawingEditTextHandler = null;
|
|
3052
|
+
let selectionChangeHandler = null;
|
|
3053
|
+
let contextMenuHandler = null;
|
|
3054
|
+
let keyboardShortcutHandler = null;
|
|
3055
|
+
let lastSelectionSignature = null;
|
|
3037
3056
|
let magnetMode = "none";
|
|
3038
3057
|
let priceScaleMode = "linear";
|
|
3039
3058
|
let priceScaleInverted = false;
|
|
@@ -3196,6 +3215,39 @@ function createChart(element, options = {}) {
|
|
|
3196
3215
|
canvas.style.setProperty("-webkit-user-select", "none");
|
|
3197
3216
|
canvas.style.setProperty("-webkit-touch-callout", "none");
|
|
3198
3217
|
canvas.setAttribute("draggable", "false");
|
|
3218
|
+
const applyAccessibilityAttributes = () => {
|
|
3219
|
+
const a11y = mergedOptions.accessibility ?? {};
|
|
3220
|
+
const focusable = a11y.focusable !== false;
|
|
3221
|
+
if (focusable) {
|
|
3222
|
+
canvas.tabIndex = 0;
|
|
3223
|
+
} else {
|
|
3224
|
+
canvas.removeAttribute("tabindex");
|
|
3225
|
+
}
|
|
3226
|
+
canvas.setAttribute("role", "application");
|
|
3227
|
+
canvas.setAttribute("aria-label", a11y.label ?? "Price chart");
|
|
3228
|
+
if (a11y.description) {
|
|
3229
|
+
canvas.setAttribute("aria-description", a11y.description);
|
|
3230
|
+
} else {
|
|
3231
|
+
canvas.removeAttribute("aria-description");
|
|
3232
|
+
}
|
|
3233
|
+
if (focusable && (mergedOptions.keyboard?.enabled ?? true)) {
|
|
3234
|
+
canvas.setAttribute(
|
|
3235
|
+
"aria-keyshortcuts",
|
|
3236
|
+
"ArrowLeft ArrowRight ArrowUp ArrowDown Plus Minus Home End Delete Escape"
|
|
3237
|
+
);
|
|
3238
|
+
} else {
|
|
3239
|
+
canvas.removeAttribute("aria-keyshortcuts");
|
|
3240
|
+
}
|
|
3241
|
+
};
|
|
3242
|
+
applyAccessibilityAttributes();
|
|
3243
|
+
let focusFromPointer = false;
|
|
3244
|
+
canvas.addEventListener("focus", () => {
|
|
3245
|
+
canvas.style.outline = focusFromPointer ? "none" : "";
|
|
3246
|
+
focusFromPointer = false;
|
|
3247
|
+
});
|
|
3248
|
+
canvas.addEventListener("blur", () => {
|
|
3249
|
+
canvas.style.outline = "none";
|
|
3250
|
+
});
|
|
3199
3251
|
element.innerHTML = "";
|
|
3200
3252
|
element.appendChild(canvas);
|
|
3201
3253
|
const baseCanvas = document.createElement("canvas");
|
|
@@ -3699,6 +3751,64 @@ function createChart(element, options = {}) {
|
|
|
3699
3751
|
heikinAshiFingerprint = fingerprint;
|
|
3700
3752
|
return result;
|
|
3701
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);
|
|
3702
3812
|
const formatHoverTimeLabel = (time, mode) => {
|
|
3703
3813
|
if (mode === "time") {
|
|
3704
3814
|
return time.toLocaleTimeString(void 0, {
|
|
@@ -4304,6 +4414,31 @@ function createChart(element, options = {}) {
|
|
|
4304
4414
|
const chartType = mergedOptions.chartType;
|
|
4305
4415
|
const seriesData = chartType === "heikin-ashi" ? getHeikinAshiData() : data;
|
|
4306
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
|
+
}
|
|
4307
4442
|
const skipLatestIndex = mergedOptions.autoScaleIgnoreLatestCandle && data.length > 1 ? data.length - 1 : -1;
|
|
4308
4443
|
const scanCandleRange = (from, to) => {
|
|
4309
4444
|
let min = Number.POSITIVE_INFINITY;
|
|
@@ -4349,6 +4484,23 @@ function createChart(element, options = {}) {
|
|
|
4349
4484
|
maxPrice = Math.max(maxPrice, weightedMax);
|
|
4350
4485
|
}
|
|
4351
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
|
+
}
|
|
4352
4504
|
const priceRange = maxPrice - minPrice || 1;
|
|
4353
4505
|
const autoMin = minPrice - priceRange * 0.08;
|
|
4354
4506
|
const autoMax = maxPrice + priceRange * 0.08;
|
|
@@ -5460,7 +5612,106 @@ function createChart(element, options = {}) {
|
|
|
5460
5612
|
}
|
|
5461
5613
|
return data[index]?.v;
|
|
5462
5614
|
};
|
|
5463
|
-
|
|
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) {
|
|
5464
5715
|
const seriesLineWidth = Math.max(1, mergedOptions.lineWidth);
|
|
5465
5716
|
const linePath = new Path2D();
|
|
5466
5717
|
let started = false;
|
|
@@ -5626,6 +5877,105 @@ function createChart(element, options = {}) {
|
|
|
5626
5877
|
ctx.stroke(downHollowBodies);
|
|
5627
5878
|
}
|
|
5628
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
|
+
}
|
|
5629
5979
|
const activeOverlayIndicators = indicators.filter((indicator) => indicator.visible).map((indicator) => ({ indicator, plugin: indicatorRegistry.get(indicator.type) })).filter(
|
|
5630
5980
|
(value) => value.plugin !== void 0 && (value.indicator.pane ?? value.plugin.pane ?? "overlay") === "overlay"
|
|
5631
5981
|
).sort((a, b) => a.indicator.zIndex - b.indicator.zIndex);
|
|
@@ -6400,6 +6750,35 @@ function createChart(element, options = {}) {
|
|
|
6400
6750
|
drawText(countdownText, chartRight + 6, (fullChartBottom + height) / 2, "left", "middle", xAxis.textColor);
|
|
6401
6751
|
ctx.font = prevFont;
|
|
6402
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
|
+
}
|
|
6403
6782
|
if (baseCtx) {
|
|
6404
6783
|
if (baseCanvas.width !== canvas.width || baseCanvas.height !== canvas.height) {
|
|
6405
6784
|
baseCanvas.width = canvas.width;
|
|
@@ -6423,6 +6802,7 @@ function createChart(element, options = {}) {
|
|
|
6423
6802
|
xSpan
|
|
6424
6803
|
};
|
|
6425
6804
|
paintCrosshairOverlay();
|
|
6805
|
+
emitSelectionChangeIfMoved();
|
|
6426
6806
|
};
|
|
6427
6807
|
const paintCrosshairOverlay = () => {
|
|
6428
6808
|
crosshairPriceActionRegion = null;
|
|
@@ -7023,6 +7403,98 @@ function createChart(element, options = {}) {
|
|
|
7023
7403
|
const ratio = (priceScaleTransform(price) - scaledMin) / scaledRange;
|
|
7024
7404
|
return priceScaleInverted ? drawState.chartTop + ratio * drawState.chartHeight : drawState.chartBottom - ratio * drawState.chartHeight;
|
|
7025
7405
|
};
|
|
7406
|
+
const getDrawingBounds = (drawing) => {
|
|
7407
|
+
if (!drawState || drawing.points.length === 0) {
|
|
7408
|
+
return null;
|
|
7409
|
+
}
|
|
7410
|
+
const { chartLeft, chartRight, chartTop, chartBottom } = drawState;
|
|
7411
|
+
let left;
|
|
7412
|
+
let right;
|
|
7413
|
+
let top;
|
|
7414
|
+
let bottom;
|
|
7415
|
+
if (drawing.type === "horizontal-line") {
|
|
7416
|
+
const y = canvasYFromDrawingPrice(drawing.points[0].price);
|
|
7417
|
+
left = chartLeft;
|
|
7418
|
+
right = chartRight;
|
|
7419
|
+
top = y;
|
|
7420
|
+
bottom = y;
|
|
7421
|
+
} else if (drawing.type === "vertical-line") {
|
|
7422
|
+
const x = canvasXFromDrawingPoint(drawing.points[0]);
|
|
7423
|
+
left = x;
|
|
7424
|
+
right = x;
|
|
7425
|
+
top = chartTop;
|
|
7426
|
+
bottom = chartBottom;
|
|
7427
|
+
} else {
|
|
7428
|
+
const xs = drawing.points.map((point) => canvasXFromDrawingPoint(point));
|
|
7429
|
+
const ys = drawing.points.map((point) => canvasYFromDrawingPrice(point.price));
|
|
7430
|
+
left = Math.min(...xs);
|
|
7431
|
+
right = Math.max(...xs);
|
|
7432
|
+
top = Math.min(...ys);
|
|
7433
|
+
bottom = Math.max(...ys);
|
|
7434
|
+
if (drawing.type === "ray") {
|
|
7435
|
+
right = chartRight;
|
|
7436
|
+
}
|
|
7437
|
+
}
|
|
7438
|
+
const clampX = (value) => clamp(value, chartLeft, chartRight);
|
|
7439
|
+
const clampY = (value) => clamp(value, chartTop, chartBottom);
|
|
7440
|
+
left = clampX(left);
|
|
7441
|
+
right = clampX(right);
|
|
7442
|
+
top = clampY(top);
|
|
7443
|
+
bottom = clampY(bottom);
|
|
7444
|
+
return {
|
|
7445
|
+
left,
|
|
7446
|
+
top,
|
|
7447
|
+
right,
|
|
7448
|
+
bottom,
|
|
7449
|
+
centerX: (left + right) / 2,
|
|
7450
|
+
centerY: (top + bottom) / 2
|
|
7451
|
+
};
|
|
7452
|
+
};
|
|
7453
|
+
const getSelectedDrawingWithBounds = () => {
|
|
7454
|
+
if (!selectedDrawingId) {
|
|
7455
|
+
return null;
|
|
7456
|
+
}
|
|
7457
|
+
const drawing = drawings.find((entry) => entry.id === selectedDrawingId);
|
|
7458
|
+
if (!drawing || !drawing.visible) {
|
|
7459
|
+
return null;
|
|
7460
|
+
}
|
|
7461
|
+
const bounds = getDrawingBounds(drawing);
|
|
7462
|
+
return bounds ? { drawing: serializeDrawing(drawing), bounds } : null;
|
|
7463
|
+
};
|
|
7464
|
+
const emitSelectionChangeIfMoved = () => {
|
|
7465
|
+
if (!selectionChangeHandler) {
|
|
7466
|
+
lastSelectionSignature = null;
|
|
7467
|
+
return;
|
|
7468
|
+
}
|
|
7469
|
+
const selection = getSelectedDrawingWithBounds();
|
|
7470
|
+
if (!selection || !drawState) {
|
|
7471
|
+
if (lastSelectionSignature !== null) {
|
|
7472
|
+
lastSelectionSignature = null;
|
|
7473
|
+
selectionChangeHandler(null);
|
|
7474
|
+
}
|
|
7475
|
+
return;
|
|
7476
|
+
}
|
|
7477
|
+
const dragging = drawingDragState !== null;
|
|
7478
|
+
const { bounds } = selection;
|
|
7479
|
+
const signature = `${selection.drawing.id}:${Math.round(bounds.left)}:${Math.round(bounds.top)}:${Math.round(
|
|
7480
|
+
bounds.right
|
|
7481
|
+
)}:${Math.round(bounds.bottom)}:${dragging ? 1 : 0}`;
|
|
7482
|
+
if (signature === lastSelectionSignature) {
|
|
7483
|
+
return;
|
|
7484
|
+
}
|
|
7485
|
+
lastSelectionSignature = signature;
|
|
7486
|
+
selectionChangeHandler({
|
|
7487
|
+
drawing: selection.drawing,
|
|
7488
|
+
bounds,
|
|
7489
|
+
plot: {
|
|
7490
|
+
left: drawState.chartLeft,
|
|
7491
|
+
top: drawState.chartTop,
|
|
7492
|
+
right: drawState.chartRight,
|
|
7493
|
+
bottom: drawState.chartBottom
|
|
7494
|
+
},
|
|
7495
|
+
dragging
|
|
7496
|
+
});
|
|
7497
|
+
};
|
|
7026
7498
|
const distanceToSegment = (x, y, x1, y1, x2, y2) => {
|
|
7027
7499
|
const dx = x2 - x1;
|
|
7028
7500
|
const dy = y2 - y1;
|
|
@@ -7989,6 +8461,10 @@ function createChart(element, options = {}) {
|
|
|
7989
8461
|
if (event.pointerType === "touch" || event.pointerType === "pen") {
|
|
7990
8462
|
event.preventDefault();
|
|
7991
8463
|
}
|
|
8464
|
+
if (mergedOptions.accessibility?.focusable !== false && document.activeElement !== canvas) {
|
|
8465
|
+
focusFromPointer = true;
|
|
8466
|
+
canvas.focus({ preventScroll: true });
|
|
8467
|
+
}
|
|
7992
8468
|
cancelKineticPan();
|
|
7993
8469
|
panVelocitySamples.length = 0;
|
|
7994
8470
|
magnetModifierActive = event.metaKey || event.ctrlKey;
|
|
@@ -8106,12 +8582,14 @@ function createChart(element, options = {}) {
|
|
|
8106
8582
|
const drawingHit = getDrawingHit(point.x, point.y);
|
|
8107
8583
|
if (drawingHit) {
|
|
8108
8584
|
selectedDrawingId = drawingHit.drawing.id;
|
|
8585
|
+
const selectBounds = getDrawingBounds(drawingHit.drawing);
|
|
8109
8586
|
drawingSelectHandler?.({
|
|
8110
8587
|
drawing: serializeDrawing(drawingHit.drawing),
|
|
8111
8588
|
target: drawingHit.target,
|
|
8112
8589
|
...drawingHit.pointIndex === void 0 ? {} : { pointIndex: drawingHit.pointIndex },
|
|
8113
8590
|
x: point.x,
|
|
8114
|
-
y: point.y
|
|
8591
|
+
y: point.y,
|
|
8592
|
+
...selectBounds ? { bounds: selectBounds } : {}
|
|
8115
8593
|
});
|
|
8116
8594
|
if (!drawingHit.drawing.locked) {
|
|
8117
8595
|
const startCanvasPoint = drawingPointFromCanvas(point.x, point.y, false);
|
|
@@ -8672,7 +9150,152 @@ function createChart(element, options = {}) {
|
|
|
8672
9150
|
};
|
|
8673
9151
|
const onContextMenu = (event) => {
|
|
8674
9152
|
event.preventDefault();
|
|
9153
|
+
if (!contextMenuHandler || !drawState) {
|
|
9154
|
+
return;
|
|
9155
|
+
}
|
|
9156
|
+
const point = getCanvasPoint(event);
|
|
9157
|
+
const hitRegion = getHitRegion(point.x, point.y);
|
|
9158
|
+
if (hitRegion === "outside") {
|
|
9159
|
+
return;
|
|
9160
|
+
}
|
|
9161
|
+
const pane = hitRegion === "plot" ? getPaneAt(point.x, point.y) : null;
|
|
9162
|
+
const drawingHit = hitRegion === "plot" && !pane ? getDrawingHit(point.x, point.y) : null;
|
|
9163
|
+
if (drawingHit) {
|
|
9164
|
+
if (selectedDrawingId !== drawingHit.drawing.id) {
|
|
9165
|
+
selectedDrawingId = drawingHit.drawing.id;
|
|
9166
|
+
scheduleDraw();
|
|
9167
|
+
}
|
|
9168
|
+
}
|
|
9169
|
+
const region = drawingHit ? "drawing" : pane ? "indicator-pane" : hitRegion;
|
|
9170
|
+
const index = indexFromCanvasX(point.x);
|
|
9171
|
+
const barTime = index === null ? null : getTimeForIndex(index);
|
|
9172
|
+
const bar = index === null ? void 0 : data[index];
|
|
9173
|
+
const inMainPane = !pane && (hitRegion === "plot" || hitRegion === "y-axis");
|
|
9174
|
+
contextMenuHandler({
|
|
9175
|
+
region,
|
|
9176
|
+
x: point.x,
|
|
9177
|
+
y: point.y,
|
|
9178
|
+
clientX: event.clientX,
|
|
9179
|
+
clientY: event.clientY,
|
|
9180
|
+
...inMainPane ? { price: roundToPricePrecision(priceFromCanvasY(point.y)) } : {},
|
|
9181
|
+
...index === null ? {} : { index },
|
|
9182
|
+
...barTime ? { time: barTime.toISOString() } : {},
|
|
9183
|
+
...bar ? {
|
|
9184
|
+
point: {
|
|
9185
|
+
t: bar.time.toISOString(),
|
|
9186
|
+
o: bar.o,
|
|
9187
|
+
h: bar.h,
|
|
9188
|
+
l: bar.l,
|
|
9189
|
+
c: bar.c,
|
|
9190
|
+
...bar.v === void 0 ? {} : { v: bar.v }
|
|
9191
|
+
}
|
|
9192
|
+
} : {},
|
|
9193
|
+
...drawingHit ? {
|
|
9194
|
+
drawing: serializeDrawing(drawingHit.drawing),
|
|
9195
|
+
drawingTarget: drawingHit.target,
|
|
9196
|
+
...drawingHit.pointIndex === void 0 ? {} : { pointIndex: drawingHit.pointIndex }
|
|
9197
|
+
} : {},
|
|
9198
|
+
...pane ? { indicator: { id: pane.id, type: pane.type } } : {}
|
|
9199
|
+
});
|
|
9200
|
+
};
|
|
9201
|
+
const onCanvasKeyDown = (event) => {
|
|
9202
|
+
const keyboard = mergedOptions.keyboard ?? {};
|
|
9203
|
+
if (keyboard.enabled === false) {
|
|
9204
|
+
return;
|
|
9205
|
+
}
|
|
9206
|
+
if (event.altKey || event.ctrlKey || event.metaKey) {
|
|
9207
|
+
return;
|
|
9208
|
+
}
|
|
9209
|
+
const panStep = Math.max(1, keyboard.panBars ?? 1) * (event.shiftKey ? 10 : 1);
|
|
9210
|
+
const emit = (action, drawing) => {
|
|
9211
|
+
keyboardShortcutHandler?.({
|
|
9212
|
+
action,
|
|
9213
|
+
key: event.key,
|
|
9214
|
+
shiftKey: event.shiftKey,
|
|
9215
|
+
...drawing ? { drawing } : {}
|
|
9216
|
+
});
|
|
9217
|
+
};
|
|
9218
|
+
switch (event.key) {
|
|
9219
|
+
case "Delete":
|
|
9220
|
+
case "Backspace": {
|
|
9221
|
+
if (keyboard.deleteSelectedDrawing === false || !selectedDrawingId) {
|
|
9222
|
+
return;
|
|
9223
|
+
}
|
|
9224
|
+
const target = drawings.find((entry) => entry.id === selectedDrawingId);
|
|
9225
|
+
if (!target) {
|
|
9226
|
+
return;
|
|
9227
|
+
}
|
|
9228
|
+
const removed = serializeDrawing(target);
|
|
9229
|
+
removeDrawing(selectedDrawingId);
|
|
9230
|
+
selectedDrawingId = null;
|
|
9231
|
+
scheduleDraw();
|
|
9232
|
+
emit("delete-drawing", removed);
|
|
9233
|
+
break;
|
|
9234
|
+
}
|
|
9235
|
+
case "Escape": {
|
|
9236
|
+
if (cancelDrawing()) {
|
|
9237
|
+
emit("cancel");
|
|
9238
|
+
break;
|
|
9239
|
+
}
|
|
9240
|
+
if (activeDrawingTool) {
|
|
9241
|
+
setActiveDrawingTool(null);
|
|
9242
|
+
emit("cancel");
|
|
9243
|
+
break;
|
|
9244
|
+
}
|
|
9245
|
+
if (selectedDrawingId) {
|
|
9246
|
+
setSelectedDrawing(null);
|
|
9247
|
+
emit("cancel");
|
|
9248
|
+
break;
|
|
9249
|
+
}
|
|
9250
|
+
return;
|
|
9251
|
+
}
|
|
9252
|
+
case "ArrowLeft":
|
|
9253
|
+
panX(-panStep);
|
|
9254
|
+
emit("pan-left");
|
|
9255
|
+
break;
|
|
9256
|
+
case "ArrowRight":
|
|
9257
|
+
panX(panStep);
|
|
9258
|
+
emit("pan-right");
|
|
9259
|
+
break;
|
|
9260
|
+
case "ArrowUp":
|
|
9261
|
+
case "ArrowDown": {
|
|
9262
|
+
if (!drawState) {
|
|
9263
|
+
return;
|
|
9264
|
+
}
|
|
9265
|
+
const step = (drawState.yMax - drawState.yMin) / 20 * (event.shiftKey ? 5 : 1);
|
|
9266
|
+
panY(event.key === "ArrowUp" ? step : -step);
|
|
9267
|
+
emit(event.key === "ArrowUp" ? "pan-up" : "pan-down");
|
|
9268
|
+
break;
|
|
9269
|
+
}
|
|
9270
|
+
case "+":
|
|
9271
|
+
case "=":
|
|
9272
|
+
zoomInX();
|
|
9273
|
+
emit("zoom-in");
|
|
9274
|
+
break;
|
|
9275
|
+
case "-":
|
|
9276
|
+
case "_":
|
|
9277
|
+
zoomOutX();
|
|
9278
|
+
emit("zoom-out");
|
|
9279
|
+
break;
|
|
9280
|
+
case "Home":
|
|
9281
|
+
fitContent();
|
|
9282
|
+
emit("reset-viewport");
|
|
9283
|
+
break;
|
|
9284
|
+
case "End":
|
|
9285
|
+
setFollowingLatest(true);
|
|
9286
|
+
emit("scroll-to-realtime");
|
|
9287
|
+
break;
|
|
9288
|
+
case "r":
|
|
9289
|
+
case "R":
|
|
9290
|
+
resetViewport();
|
|
9291
|
+
emit("reset-viewport");
|
|
9292
|
+
break;
|
|
9293
|
+
default:
|
|
9294
|
+
return;
|
|
9295
|
+
}
|
|
9296
|
+
event.preventDefault();
|
|
8675
9297
|
};
|
|
9298
|
+
canvas.addEventListener("keydown", onCanvasKeyDown);
|
|
8676
9299
|
canvas.addEventListener("pointerdown", onPointerDown);
|
|
8677
9300
|
canvas.addEventListener("pointermove", onPointerMove);
|
|
8678
9301
|
canvas.addEventListener("pointerup", endPointerDrag);
|
|
@@ -8719,6 +9342,7 @@ function createChart(element, options = {}) {
|
|
|
8719
9342
|
resetRightMarginCache();
|
|
8720
9343
|
doubleClickEnabled = mergedOptions.doubleClickEnabled;
|
|
8721
9344
|
doubleClickAction = mergedOptions.doubleClickAction;
|
|
9345
|
+
applyAccessibilityAttributes();
|
|
8722
9346
|
const isTickerSmoothingEnabled = mergedOptions.tickerLine?.smoothing ?? false;
|
|
8723
9347
|
if (!isTickerSmoothingEnabled) {
|
|
8724
9348
|
smoothedTickerPrice = null;
|
|
@@ -9102,6 +9726,23 @@ function createChart(element, options = {}) {
|
|
|
9102
9726
|
const onDrawingHover = (handler) => {
|
|
9103
9727
|
drawingHoverHandler = handler;
|
|
9104
9728
|
};
|
|
9729
|
+
const onSelectionChange = (handler) => {
|
|
9730
|
+
selectionChangeHandler = handler;
|
|
9731
|
+
lastSelectionSignature = null;
|
|
9732
|
+
if (handler) {
|
|
9733
|
+
emitSelectionChangeIfMoved();
|
|
9734
|
+
}
|
|
9735
|
+
};
|
|
9736
|
+
const getSelectedDrawing = () => getSelectedDrawingWithBounds();
|
|
9737
|
+
const onContextMenuHandler = (handler) => {
|
|
9738
|
+
contextMenuHandler = handler;
|
|
9739
|
+
};
|
|
9740
|
+
const onKeyboardShortcut = (handler) => {
|
|
9741
|
+
keyboardShortcutHandler = handler;
|
|
9742
|
+
};
|
|
9743
|
+
const focus = () => {
|
|
9744
|
+
canvas.focus({ preventScroll: true });
|
|
9745
|
+
};
|
|
9105
9746
|
const saveState = () => {
|
|
9106
9747
|
const drawingDefaults = {};
|
|
9107
9748
|
for (const [tool, defaults] of drawingToolDefaults) {
|
|
@@ -9166,6 +9807,7 @@ function createChart(element, options = {}) {
|
|
|
9166
9807
|
drawRafId = null;
|
|
9167
9808
|
pendingDrawUpdateAutoScale = false;
|
|
9168
9809
|
}
|
|
9810
|
+
canvas.removeEventListener("keydown", onCanvasKeyDown);
|
|
9169
9811
|
canvas.removeEventListener("pointerdown", onPointerDown);
|
|
9170
9812
|
canvas.removeEventListener("pointermove", onPointerMove);
|
|
9171
9813
|
canvas.removeEventListener("pointerup", endPointerDrag);
|
|
@@ -9229,10 +9871,19 @@ function createChart(element, options = {}) {
|
|
|
9229
9871
|
onDrawingSelect,
|
|
9230
9872
|
onDrawingDoubleClick,
|
|
9231
9873
|
onDrawingEditText,
|
|
9874
|
+
onSelectionChange,
|
|
9875
|
+
getSelectedDrawing,
|
|
9876
|
+
onContextMenu: onContextMenuHandler,
|
|
9877
|
+
onKeyboardShortcut,
|
|
9878
|
+
focus,
|
|
9232
9879
|
setMagnetMode,
|
|
9233
9880
|
getMagnetMode,
|
|
9234
9881
|
setPriceScale,
|
|
9235
9882
|
getPriceScale,
|
|
9883
|
+
setCompareSeries,
|
|
9884
|
+
addCompareSeries,
|
|
9885
|
+
removeCompareSeries,
|
|
9886
|
+
getCompareSeries,
|
|
9236
9887
|
cancelDrawing,
|
|
9237
9888
|
setTradeMarkers,
|
|
9238
9889
|
setSelectedDrawing,
|