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.cjs
CHANGED
|
@@ -2851,6 +2851,9 @@ var DEFAULT_OPTIONS = {
|
|
|
2851
2851
|
pinOutOfRangeLines: false,
|
|
2852
2852
|
doubleClickEnabled: true,
|
|
2853
2853
|
doubleClickAction: "reset",
|
|
2854
|
+
keyboard: { enabled: true, panBars: 1, deleteSelectedDrawing: true },
|
|
2855
|
+
accessibility: { label: "Price chart", description: "", focusable: true },
|
|
2856
|
+
downsampling: { enabled: true, thresholdPx: 1.5 },
|
|
2854
2857
|
crosshair: DEFAULT_CROSSHAIR_OPTIONS,
|
|
2855
2858
|
grid: DEFAULT_GRID_OPTIONS,
|
|
2856
2859
|
watermark: DEFAULT_WATERMARK_OPTIONS,
|
|
@@ -2893,6 +2896,18 @@ var mergeChartOptions = (baseOptions, options = {}) => ({
|
|
|
2893
2896
|
...options.axisColor ? { lineColor: options.axisColor, textColor: options.axisColor } : {},
|
|
2894
2897
|
...options.yAxis ?? {}
|
|
2895
2898
|
},
|
|
2899
|
+
keyboard: {
|
|
2900
|
+
...baseOptions.keyboard,
|
|
2901
|
+
...options.keyboard ?? {}
|
|
2902
|
+
},
|
|
2903
|
+
accessibility: {
|
|
2904
|
+
...baseOptions.accessibility,
|
|
2905
|
+
...options.accessibility ?? {}
|
|
2906
|
+
},
|
|
2907
|
+
downsampling: {
|
|
2908
|
+
...baseOptions.downsampling,
|
|
2909
|
+
...options.downsampling ?? {}
|
|
2910
|
+
},
|
|
2896
2911
|
crosshair: {
|
|
2897
2912
|
...baseOptions.crosshair,
|
|
2898
2913
|
...options.crosshair ?? {}
|
|
@@ -3066,6 +3081,10 @@ function createChart(element, options = {}) {
|
|
|
3066
3081
|
let drawingSelectHandler = null;
|
|
3067
3082
|
let drawingDoubleClickHandler = null;
|
|
3068
3083
|
let drawingEditTextHandler = null;
|
|
3084
|
+
let selectionChangeHandler = null;
|
|
3085
|
+
let contextMenuHandler = null;
|
|
3086
|
+
let keyboardShortcutHandler = null;
|
|
3087
|
+
let lastSelectionSignature = null;
|
|
3069
3088
|
let magnetMode = "none";
|
|
3070
3089
|
let priceScaleMode = "linear";
|
|
3071
3090
|
let priceScaleInverted = false;
|
|
@@ -3228,6 +3247,39 @@ function createChart(element, options = {}) {
|
|
|
3228
3247
|
canvas.style.setProperty("-webkit-user-select", "none");
|
|
3229
3248
|
canvas.style.setProperty("-webkit-touch-callout", "none");
|
|
3230
3249
|
canvas.setAttribute("draggable", "false");
|
|
3250
|
+
const applyAccessibilityAttributes = () => {
|
|
3251
|
+
const a11y = mergedOptions.accessibility ?? {};
|
|
3252
|
+
const focusable = a11y.focusable !== false;
|
|
3253
|
+
if (focusable) {
|
|
3254
|
+
canvas.tabIndex = 0;
|
|
3255
|
+
} else {
|
|
3256
|
+
canvas.removeAttribute("tabindex");
|
|
3257
|
+
}
|
|
3258
|
+
canvas.setAttribute("role", "application");
|
|
3259
|
+
canvas.setAttribute("aria-label", a11y.label ?? "Price chart");
|
|
3260
|
+
if (a11y.description) {
|
|
3261
|
+
canvas.setAttribute("aria-description", a11y.description);
|
|
3262
|
+
} else {
|
|
3263
|
+
canvas.removeAttribute("aria-description");
|
|
3264
|
+
}
|
|
3265
|
+
if (focusable && (mergedOptions.keyboard?.enabled ?? true)) {
|
|
3266
|
+
canvas.setAttribute(
|
|
3267
|
+
"aria-keyshortcuts",
|
|
3268
|
+
"ArrowLeft ArrowRight ArrowUp ArrowDown Plus Minus Home End Delete Escape"
|
|
3269
|
+
);
|
|
3270
|
+
} else {
|
|
3271
|
+
canvas.removeAttribute("aria-keyshortcuts");
|
|
3272
|
+
}
|
|
3273
|
+
};
|
|
3274
|
+
applyAccessibilityAttributes();
|
|
3275
|
+
let focusFromPointer = false;
|
|
3276
|
+
canvas.addEventListener("focus", () => {
|
|
3277
|
+
canvas.style.outline = focusFromPointer ? "none" : "";
|
|
3278
|
+
focusFromPointer = false;
|
|
3279
|
+
});
|
|
3280
|
+
canvas.addEventListener("blur", () => {
|
|
3281
|
+
canvas.style.outline = "none";
|
|
3282
|
+
});
|
|
3231
3283
|
element.innerHTML = "";
|
|
3232
3284
|
element.appendChild(canvas);
|
|
3233
3285
|
const baseCanvas = document.createElement("canvas");
|
|
@@ -3731,6 +3783,64 @@ function createChart(element, options = {}) {
|
|
|
3731
3783
|
heikinAshiFingerprint = fingerprint;
|
|
3732
3784
|
return result;
|
|
3733
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);
|
|
3734
3844
|
const formatHoverTimeLabel = (time, mode) => {
|
|
3735
3845
|
if (mode === "time") {
|
|
3736
3846
|
return time.toLocaleTimeString(void 0, {
|
|
@@ -4336,6 +4446,31 @@ function createChart(element, options = {}) {
|
|
|
4336
4446
|
const chartType = mergedOptions.chartType;
|
|
4337
4447
|
const seriesData = chartType === "heikin-ashi" ? getHeikinAshiData() : data;
|
|
4338
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
|
+
}
|
|
4339
4474
|
const skipLatestIndex = mergedOptions.autoScaleIgnoreLatestCandle && data.length > 1 ? data.length - 1 : -1;
|
|
4340
4475
|
const scanCandleRange = (from, to) => {
|
|
4341
4476
|
let min = Number.POSITIVE_INFINITY;
|
|
@@ -4381,6 +4516,23 @@ function createChart(element, options = {}) {
|
|
|
4381
4516
|
maxPrice = Math.max(maxPrice, weightedMax);
|
|
4382
4517
|
}
|
|
4383
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
|
+
}
|
|
4384
4536
|
const priceRange = maxPrice - minPrice || 1;
|
|
4385
4537
|
const autoMin = minPrice - priceRange * 0.08;
|
|
4386
4538
|
const autoMax = maxPrice + priceRange * 0.08;
|
|
@@ -5492,7 +5644,106 @@ function createChart(element, options = {}) {
|
|
|
5492
5644
|
}
|
|
5493
5645
|
return data[index]?.v;
|
|
5494
5646
|
};
|
|
5495
|
-
|
|
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) {
|
|
5496
5747
|
const seriesLineWidth = Math.max(1, mergedOptions.lineWidth);
|
|
5497
5748
|
const linePath = new Path2D();
|
|
5498
5749
|
let started = false;
|
|
@@ -5658,6 +5909,105 @@ function createChart(element, options = {}) {
|
|
|
5658
5909
|
ctx.stroke(downHollowBodies);
|
|
5659
5910
|
}
|
|
5660
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
|
+
}
|
|
5661
6011
|
const activeOverlayIndicators = indicators.filter((indicator) => indicator.visible).map((indicator) => ({ indicator, plugin: indicatorRegistry.get(indicator.type) })).filter(
|
|
5662
6012
|
(value) => value.plugin !== void 0 && (value.indicator.pane ?? value.plugin.pane ?? "overlay") === "overlay"
|
|
5663
6013
|
).sort((a, b) => a.indicator.zIndex - b.indicator.zIndex);
|
|
@@ -6432,6 +6782,35 @@ function createChart(element, options = {}) {
|
|
|
6432
6782
|
drawText(countdownText, chartRight + 6, (fullChartBottom + height) / 2, "left", "middle", xAxis.textColor);
|
|
6433
6783
|
ctx.font = prevFont;
|
|
6434
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
|
+
}
|
|
6435
6814
|
if (baseCtx) {
|
|
6436
6815
|
if (baseCanvas.width !== canvas.width || baseCanvas.height !== canvas.height) {
|
|
6437
6816
|
baseCanvas.width = canvas.width;
|
|
@@ -6455,6 +6834,7 @@ function createChart(element, options = {}) {
|
|
|
6455
6834
|
xSpan
|
|
6456
6835
|
};
|
|
6457
6836
|
paintCrosshairOverlay();
|
|
6837
|
+
emitSelectionChangeIfMoved();
|
|
6458
6838
|
};
|
|
6459
6839
|
const paintCrosshairOverlay = () => {
|
|
6460
6840
|
crosshairPriceActionRegion = null;
|
|
@@ -7055,6 +7435,98 @@ function createChart(element, options = {}) {
|
|
|
7055
7435
|
const ratio = (priceScaleTransform(price) - scaledMin) / scaledRange;
|
|
7056
7436
|
return priceScaleInverted ? drawState.chartTop + ratio * drawState.chartHeight : drawState.chartBottom - ratio * drawState.chartHeight;
|
|
7057
7437
|
};
|
|
7438
|
+
const getDrawingBounds = (drawing) => {
|
|
7439
|
+
if (!drawState || drawing.points.length === 0) {
|
|
7440
|
+
return null;
|
|
7441
|
+
}
|
|
7442
|
+
const { chartLeft, chartRight, chartTop, chartBottom } = drawState;
|
|
7443
|
+
let left;
|
|
7444
|
+
let right;
|
|
7445
|
+
let top;
|
|
7446
|
+
let bottom;
|
|
7447
|
+
if (drawing.type === "horizontal-line") {
|
|
7448
|
+
const y = canvasYFromDrawingPrice(drawing.points[0].price);
|
|
7449
|
+
left = chartLeft;
|
|
7450
|
+
right = chartRight;
|
|
7451
|
+
top = y;
|
|
7452
|
+
bottom = y;
|
|
7453
|
+
} else if (drawing.type === "vertical-line") {
|
|
7454
|
+
const x = canvasXFromDrawingPoint(drawing.points[0]);
|
|
7455
|
+
left = x;
|
|
7456
|
+
right = x;
|
|
7457
|
+
top = chartTop;
|
|
7458
|
+
bottom = chartBottom;
|
|
7459
|
+
} else {
|
|
7460
|
+
const xs = drawing.points.map((point) => canvasXFromDrawingPoint(point));
|
|
7461
|
+
const ys = drawing.points.map((point) => canvasYFromDrawingPrice(point.price));
|
|
7462
|
+
left = Math.min(...xs);
|
|
7463
|
+
right = Math.max(...xs);
|
|
7464
|
+
top = Math.min(...ys);
|
|
7465
|
+
bottom = Math.max(...ys);
|
|
7466
|
+
if (drawing.type === "ray") {
|
|
7467
|
+
right = chartRight;
|
|
7468
|
+
}
|
|
7469
|
+
}
|
|
7470
|
+
const clampX = (value) => clamp(value, chartLeft, chartRight);
|
|
7471
|
+
const clampY = (value) => clamp(value, chartTop, chartBottom);
|
|
7472
|
+
left = clampX(left);
|
|
7473
|
+
right = clampX(right);
|
|
7474
|
+
top = clampY(top);
|
|
7475
|
+
bottom = clampY(bottom);
|
|
7476
|
+
return {
|
|
7477
|
+
left,
|
|
7478
|
+
top,
|
|
7479
|
+
right,
|
|
7480
|
+
bottom,
|
|
7481
|
+
centerX: (left + right) / 2,
|
|
7482
|
+
centerY: (top + bottom) / 2
|
|
7483
|
+
};
|
|
7484
|
+
};
|
|
7485
|
+
const getSelectedDrawingWithBounds = () => {
|
|
7486
|
+
if (!selectedDrawingId) {
|
|
7487
|
+
return null;
|
|
7488
|
+
}
|
|
7489
|
+
const drawing = drawings.find((entry) => entry.id === selectedDrawingId);
|
|
7490
|
+
if (!drawing || !drawing.visible) {
|
|
7491
|
+
return null;
|
|
7492
|
+
}
|
|
7493
|
+
const bounds = getDrawingBounds(drawing);
|
|
7494
|
+
return bounds ? { drawing: serializeDrawing(drawing), bounds } : null;
|
|
7495
|
+
};
|
|
7496
|
+
const emitSelectionChangeIfMoved = () => {
|
|
7497
|
+
if (!selectionChangeHandler) {
|
|
7498
|
+
lastSelectionSignature = null;
|
|
7499
|
+
return;
|
|
7500
|
+
}
|
|
7501
|
+
const selection = getSelectedDrawingWithBounds();
|
|
7502
|
+
if (!selection || !drawState) {
|
|
7503
|
+
if (lastSelectionSignature !== null) {
|
|
7504
|
+
lastSelectionSignature = null;
|
|
7505
|
+
selectionChangeHandler(null);
|
|
7506
|
+
}
|
|
7507
|
+
return;
|
|
7508
|
+
}
|
|
7509
|
+
const dragging = drawingDragState !== null;
|
|
7510
|
+
const { bounds } = selection;
|
|
7511
|
+
const signature = `${selection.drawing.id}:${Math.round(bounds.left)}:${Math.round(bounds.top)}:${Math.round(
|
|
7512
|
+
bounds.right
|
|
7513
|
+
)}:${Math.round(bounds.bottom)}:${dragging ? 1 : 0}`;
|
|
7514
|
+
if (signature === lastSelectionSignature) {
|
|
7515
|
+
return;
|
|
7516
|
+
}
|
|
7517
|
+
lastSelectionSignature = signature;
|
|
7518
|
+
selectionChangeHandler({
|
|
7519
|
+
drawing: selection.drawing,
|
|
7520
|
+
bounds,
|
|
7521
|
+
plot: {
|
|
7522
|
+
left: drawState.chartLeft,
|
|
7523
|
+
top: drawState.chartTop,
|
|
7524
|
+
right: drawState.chartRight,
|
|
7525
|
+
bottom: drawState.chartBottom
|
|
7526
|
+
},
|
|
7527
|
+
dragging
|
|
7528
|
+
});
|
|
7529
|
+
};
|
|
7058
7530
|
const distanceToSegment = (x, y, x1, y1, x2, y2) => {
|
|
7059
7531
|
const dx = x2 - x1;
|
|
7060
7532
|
const dy = y2 - y1;
|
|
@@ -8021,6 +8493,10 @@ function createChart(element, options = {}) {
|
|
|
8021
8493
|
if (event.pointerType === "touch" || event.pointerType === "pen") {
|
|
8022
8494
|
event.preventDefault();
|
|
8023
8495
|
}
|
|
8496
|
+
if (mergedOptions.accessibility?.focusable !== false && document.activeElement !== canvas) {
|
|
8497
|
+
focusFromPointer = true;
|
|
8498
|
+
canvas.focus({ preventScroll: true });
|
|
8499
|
+
}
|
|
8024
8500
|
cancelKineticPan();
|
|
8025
8501
|
panVelocitySamples.length = 0;
|
|
8026
8502
|
magnetModifierActive = event.metaKey || event.ctrlKey;
|
|
@@ -8138,12 +8614,14 @@ function createChart(element, options = {}) {
|
|
|
8138
8614
|
const drawingHit = getDrawingHit(point.x, point.y);
|
|
8139
8615
|
if (drawingHit) {
|
|
8140
8616
|
selectedDrawingId = drawingHit.drawing.id;
|
|
8617
|
+
const selectBounds = getDrawingBounds(drawingHit.drawing);
|
|
8141
8618
|
drawingSelectHandler?.({
|
|
8142
8619
|
drawing: serializeDrawing(drawingHit.drawing),
|
|
8143
8620
|
target: drawingHit.target,
|
|
8144
8621
|
...drawingHit.pointIndex === void 0 ? {} : { pointIndex: drawingHit.pointIndex },
|
|
8145
8622
|
x: point.x,
|
|
8146
|
-
y: point.y
|
|
8623
|
+
y: point.y,
|
|
8624
|
+
...selectBounds ? { bounds: selectBounds } : {}
|
|
8147
8625
|
});
|
|
8148
8626
|
if (!drawingHit.drawing.locked) {
|
|
8149
8627
|
const startCanvasPoint = drawingPointFromCanvas(point.x, point.y, false);
|
|
@@ -8704,7 +9182,152 @@ function createChart(element, options = {}) {
|
|
|
8704
9182
|
};
|
|
8705
9183
|
const onContextMenu = (event) => {
|
|
8706
9184
|
event.preventDefault();
|
|
9185
|
+
if (!contextMenuHandler || !drawState) {
|
|
9186
|
+
return;
|
|
9187
|
+
}
|
|
9188
|
+
const point = getCanvasPoint(event);
|
|
9189
|
+
const hitRegion = getHitRegion(point.x, point.y);
|
|
9190
|
+
if (hitRegion === "outside") {
|
|
9191
|
+
return;
|
|
9192
|
+
}
|
|
9193
|
+
const pane = hitRegion === "plot" ? getPaneAt(point.x, point.y) : null;
|
|
9194
|
+
const drawingHit = hitRegion === "plot" && !pane ? getDrawingHit(point.x, point.y) : null;
|
|
9195
|
+
if (drawingHit) {
|
|
9196
|
+
if (selectedDrawingId !== drawingHit.drawing.id) {
|
|
9197
|
+
selectedDrawingId = drawingHit.drawing.id;
|
|
9198
|
+
scheduleDraw();
|
|
9199
|
+
}
|
|
9200
|
+
}
|
|
9201
|
+
const region = drawingHit ? "drawing" : pane ? "indicator-pane" : hitRegion;
|
|
9202
|
+
const index = indexFromCanvasX(point.x);
|
|
9203
|
+
const barTime = index === null ? null : getTimeForIndex(index);
|
|
9204
|
+
const bar = index === null ? void 0 : data[index];
|
|
9205
|
+
const inMainPane = !pane && (hitRegion === "plot" || hitRegion === "y-axis");
|
|
9206
|
+
contextMenuHandler({
|
|
9207
|
+
region,
|
|
9208
|
+
x: point.x,
|
|
9209
|
+
y: point.y,
|
|
9210
|
+
clientX: event.clientX,
|
|
9211
|
+
clientY: event.clientY,
|
|
9212
|
+
...inMainPane ? { price: roundToPricePrecision(priceFromCanvasY(point.y)) } : {},
|
|
9213
|
+
...index === null ? {} : { index },
|
|
9214
|
+
...barTime ? { time: barTime.toISOString() } : {},
|
|
9215
|
+
...bar ? {
|
|
9216
|
+
point: {
|
|
9217
|
+
t: bar.time.toISOString(),
|
|
9218
|
+
o: bar.o,
|
|
9219
|
+
h: bar.h,
|
|
9220
|
+
l: bar.l,
|
|
9221
|
+
c: bar.c,
|
|
9222
|
+
...bar.v === void 0 ? {} : { v: bar.v }
|
|
9223
|
+
}
|
|
9224
|
+
} : {},
|
|
9225
|
+
...drawingHit ? {
|
|
9226
|
+
drawing: serializeDrawing(drawingHit.drawing),
|
|
9227
|
+
drawingTarget: drawingHit.target,
|
|
9228
|
+
...drawingHit.pointIndex === void 0 ? {} : { pointIndex: drawingHit.pointIndex }
|
|
9229
|
+
} : {},
|
|
9230
|
+
...pane ? { indicator: { id: pane.id, type: pane.type } } : {}
|
|
9231
|
+
});
|
|
9232
|
+
};
|
|
9233
|
+
const onCanvasKeyDown = (event) => {
|
|
9234
|
+
const keyboard = mergedOptions.keyboard ?? {};
|
|
9235
|
+
if (keyboard.enabled === false) {
|
|
9236
|
+
return;
|
|
9237
|
+
}
|
|
9238
|
+
if (event.altKey || event.ctrlKey || event.metaKey) {
|
|
9239
|
+
return;
|
|
9240
|
+
}
|
|
9241
|
+
const panStep = Math.max(1, keyboard.panBars ?? 1) * (event.shiftKey ? 10 : 1);
|
|
9242
|
+
const emit = (action, drawing) => {
|
|
9243
|
+
keyboardShortcutHandler?.({
|
|
9244
|
+
action,
|
|
9245
|
+
key: event.key,
|
|
9246
|
+
shiftKey: event.shiftKey,
|
|
9247
|
+
...drawing ? { drawing } : {}
|
|
9248
|
+
});
|
|
9249
|
+
};
|
|
9250
|
+
switch (event.key) {
|
|
9251
|
+
case "Delete":
|
|
9252
|
+
case "Backspace": {
|
|
9253
|
+
if (keyboard.deleteSelectedDrawing === false || !selectedDrawingId) {
|
|
9254
|
+
return;
|
|
9255
|
+
}
|
|
9256
|
+
const target = drawings.find((entry) => entry.id === selectedDrawingId);
|
|
9257
|
+
if (!target) {
|
|
9258
|
+
return;
|
|
9259
|
+
}
|
|
9260
|
+
const removed = serializeDrawing(target);
|
|
9261
|
+
removeDrawing(selectedDrawingId);
|
|
9262
|
+
selectedDrawingId = null;
|
|
9263
|
+
scheduleDraw();
|
|
9264
|
+
emit("delete-drawing", removed);
|
|
9265
|
+
break;
|
|
9266
|
+
}
|
|
9267
|
+
case "Escape": {
|
|
9268
|
+
if (cancelDrawing()) {
|
|
9269
|
+
emit("cancel");
|
|
9270
|
+
break;
|
|
9271
|
+
}
|
|
9272
|
+
if (activeDrawingTool) {
|
|
9273
|
+
setActiveDrawingTool(null);
|
|
9274
|
+
emit("cancel");
|
|
9275
|
+
break;
|
|
9276
|
+
}
|
|
9277
|
+
if (selectedDrawingId) {
|
|
9278
|
+
setSelectedDrawing(null);
|
|
9279
|
+
emit("cancel");
|
|
9280
|
+
break;
|
|
9281
|
+
}
|
|
9282
|
+
return;
|
|
9283
|
+
}
|
|
9284
|
+
case "ArrowLeft":
|
|
9285
|
+
panX(-panStep);
|
|
9286
|
+
emit("pan-left");
|
|
9287
|
+
break;
|
|
9288
|
+
case "ArrowRight":
|
|
9289
|
+
panX(panStep);
|
|
9290
|
+
emit("pan-right");
|
|
9291
|
+
break;
|
|
9292
|
+
case "ArrowUp":
|
|
9293
|
+
case "ArrowDown": {
|
|
9294
|
+
if (!drawState) {
|
|
9295
|
+
return;
|
|
9296
|
+
}
|
|
9297
|
+
const step = (drawState.yMax - drawState.yMin) / 20 * (event.shiftKey ? 5 : 1);
|
|
9298
|
+
panY(event.key === "ArrowUp" ? step : -step);
|
|
9299
|
+
emit(event.key === "ArrowUp" ? "pan-up" : "pan-down");
|
|
9300
|
+
break;
|
|
9301
|
+
}
|
|
9302
|
+
case "+":
|
|
9303
|
+
case "=":
|
|
9304
|
+
zoomInX();
|
|
9305
|
+
emit("zoom-in");
|
|
9306
|
+
break;
|
|
9307
|
+
case "-":
|
|
9308
|
+
case "_":
|
|
9309
|
+
zoomOutX();
|
|
9310
|
+
emit("zoom-out");
|
|
9311
|
+
break;
|
|
9312
|
+
case "Home":
|
|
9313
|
+
fitContent();
|
|
9314
|
+
emit("reset-viewport");
|
|
9315
|
+
break;
|
|
9316
|
+
case "End":
|
|
9317
|
+
setFollowingLatest(true);
|
|
9318
|
+
emit("scroll-to-realtime");
|
|
9319
|
+
break;
|
|
9320
|
+
case "r":
|
|
9321
|
+
case "R":
|
|
9322
|
+
resetViewport();
|
|
9323
|
+
emit("reset-viewport");
|
|
9324
|
+
break;
|
|
9325
|
+
default:
|
|
9326
|
+
return;
|
|
9327
|
+
}
|
|
9328
|
+
event.preventDefault();
|
|
8707
9329
|
};
|
|
9330
|
+
canvas.addEventListener("keydown", onCanvasKeyDown);
|
|
8708
9331
|
canvas.addEventListener("pointerdown", onPointerDown);
|
|
8709
9332
|
canvas.addEventListener("pointermove", onPointerMove);
|
|
8710
9333
|
canvas.addEventListener("pointerup", endPointerDrag);
|
|
@@ -8751,6 +9374,7 @@ function createChart(element, options = {}) {
|
|
|
8751
9374
|
resetRightMarginCache();
|
|
8752
9375
|
doubleClickEnabled = mergedOptions.doubleClickEnabled;
|
|
8753
9376
|
doubleClickAction = mergedOptions.doubleClickAction;
|
|
9377
|
+
applyAccessibilityAttributes();
|
|
8754
9378
|
const isTickerSmoothingEnabled = mergedOptions.tickerLine?.smoothing ?? false;
|
|
8755
9379
|
if (!isTickerSmoothingEnabled) {
|
|
8756
9380
|
smoothedTickerPrice = null;
|
|
@@ -9134,6 +9758,23 @@ function createChart(element, options = {}) {
|
|
|
9134
9758
|
const onDrawingHover = (handler) => {
|
|
9135
9759
|
drawingHoverHandler = handler;
|
|
9136
9760
|
};
|
|
9761
|
+
const onSelectionChange = (handler) => {
|
|
9762
|
+
selectionChangeHandler = handler;
|
|
9763
|
+
lastSelectionSignature = null;
|
|
9764
|
+
if (handler) {
|
|
9765
|
+
emitSelectionChangeIfMoved();
|
|
9766
|
+
}
|
|
9767
|
+
};
|
|
9768
|
+
const getSelectedDrawing = () => getSelectedDrawingWithBounds();
|
|
9769
|
+
const onContextMenuHandler = (handler) => {
|
|
9770
|
+
contextMenuHandler = handler;
|
|
9771
|
+
};
|
|
9772
|
+
const onKeyboardShortcut = (handler) => {
|
|
9773
|
+
keyboardShortcutHandler = handler;
|
|
9774
|
+
};
|
|
9775
|
+
const focus = () => {
|
|
9776
|
+
canvas.focus({ preventScroll: true });
|
|
9777
|
+
};
|
|
9137
9778
|
const saveState = () => {
|
|
9138
9779
|
const drawingDefaults = {};
|
|
9139
9780
|
for (const [tool, defaults] of drawingToolDefaults) {
|
|
@@ -9198,6 +9839,7 @@ function createChart(element, options = {}) {
|
|
|
9198
9839
|
drawRafId = null;
|
|
9199
9840
|
pendingDrawUpdateAutoScale = false;
|
|
9200
9841
|
}
|
|
9842
|
+
canvas.removeEventListener("keydown", onCanvasKeyDown);
|
|
9201
9843
|
canvas.removeEventListener("pointerdown", onPointerDown);
|
|
9202
9844
|
canvas.removeEventListener("pointermove", onPointerMove);
|
|
9203
9845
|
canvas.removeEventListener("pointerup", endPointerDrag);
|
|
@@ -9261,10 +9903,19 @@ function createChart(element, options = {}) {
|
|
|
9261
9903
|
onDrawingSelect,
|
|
9262
9904
|
onDrawingDoubleClick,
|
|
9263
9905
|
onDrawingEditText,
|
|
9906
|
+
onSelectionChange,
|
|
9907
|
+
getSelectedDrawing,
|
|
9908
|
+
onContextMenu: onContextMenuHandler,
|
|
9909
|
+
onKeyboardShortcut,
|
|
9910
|
+
focus,
|
|
9264
9911
|
setMagnetMode,
|
|
9265
9912
|
getMagnetMode,
|
|
9266
9913
|
setPriceScale,
|
|
9267
9914
|
getPriceScale,
|
|
9915
|
+
setCompareSeries,
|
|
9916
|
+
addCompareSeries,
|
|
9917
|
+
removeCompareSeries,
|
|
9918
|
+
getCompareSeries,
|
|
9268
9919
|
cancelDrawing,
|
|
9269
9920
|
setTradeMarkers,
|
|
9270
9921
|
setSelectedDrawing,
|