hyperprop-charting-library 0.1.136 → 0.1.138
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 +519 -43
- package/dist/hyperprop-charting-library.d.ts +172 -1
- package/dist/hyperprop-charting-library.js +517 -43
- package/dist/index.cjs +519 -43
- package/dist/index.d.cts +172 -1
- package/dist/index.d.ts +172 -1
- package/dist/index.js +517 -43
- package/docs/API.md +139 -1
- package/package.json +1 -1
package/dist/index.js
CHANGED
|
@@ -2121,6 +2121,82 @@ var hashScriptSource = (source) => {
|
|
|
2121
2121
|
}
|
|
2122
2122
|
return (hash >>> 0).toString(36);
|
|
2123
2123
|
};
|
|
2124
|
+
var SCRIPT_SOURCE_INPUT_VALUES = ["open", "high", "low", "close", "hl2", "hlc3", "ohlc4"];
|
|
2125
|
+
var slugifyInputLabel = (label) => {
|
|
2126
|
+
const slug = String(label).trim().toLowerCase().replace(/[^a-z0-9]+/g, "_").replace(/^_+|_+$/g, "");
|
|
2127
|
+
return slug || "input";
|
|
2128
|
+
};
|
|
2129
|
+
var createScriptInputHelper = (values, registry) => {
|
|
2130
|
+
const usedKeys = /* @__PURE__ */ new Set();
|
|
2131
|
+
const resolveKey = (label, options) => {
|
|
2132
|
+
let key = options?.key ?? slugifyInputLabel(label);
|
|
2133
|
+
if (usedKeys.has(key)) {
|
|
2134
|
+
let suffix = 2;
|
|
2135
|
+
while (usedKeys.has(`${key}_${suffix}`)) suffix += 1;
|
|
2136
|
+
key = `${key}_${suffix}`;
|
|
2137
|
+
}
|
|
2138
|
+
usedKeys.add(key);
|
|
2139
|
+
return key;
|
|
2140
|
+
};
|
|
2141
|
+
const resolve = (key, fallback, coerce) => {
|
|
2142
|
+
if (!values) return fallback;
|
|
2143
|
+
const raw = values[key];
|
|
2144
|
+
if (raw === void 0 || raw === null) return fallback;
|
|
2145
|
+
const coerced = coerce(raw);
|
|
2146
|
+
return coerced === void 0 ? fallback : coerced;
|
|
2147
|
+
};
|
|
2148
|
+
const coerceNumber = (raw) => {
|
|
2149
|
+
const num = typeof raw === "number" ? raw : Number(raw);
|
|
2150
|
+
return Number.isFinite(num) ? num : void 0;
|
|
2151
|
+
};
|
|
2152
|
+
const numberInput = (integer) => (label, defaultValue, options) => {
|
|
2153
|
+
const key = resolveKey(label, options);
|
|
2154
|
+
registry?.push({
|
|
2155
|
+
key,
|
|
2156
|
+
label,
|
|
2157
|
+
type: "number",
|
|
2158
|
+
default: defaultValue,
|
|
2159
|
+
...options?.min !== void 0 ? { min: options.min } : {},
|
|
2160
|
+
...options?.max !== void 0 ? { max: options.max } : {},
|
|
2161
|
+
...options?.step !== void 0 ? { step: options.step } : integer ? { step: 1 } : {}
|
|
2162
|
+
});
|
|
2163
|
+
const value = resolve(key, defaultValue, coerceNumber);
|
|
2164
|
+
return integer ? Math.round(value) : value;
|
|
2165
|
+
};
|
|
2166
|
+
const selectInput = (label, defaultValue, choices, options) => {
|
|
2167
|
+
const key = resolveKey(label, options);
|
|
2168
|
+
const normalized = (Array.isArray(choices) ? choices : []).map(
|
|
2169
|
+
(choice) => typeof choice === "string" ? { value: choice, label: choice } : { value: String(choice.value), label: String(choice.label ?? choice.value) }
|
|
2170
|
+
);
|
|
2171
|
+
registry?.push({ key, label, type: "select", default: defaultValue, options: normalized });
|
|
2172
|
+
return resolve(key, defaultValue, (raw) => typeof raw === "string" ? raw : String(raw));
|
|
2173
|
+
};
|
|
2174
|
+
return Object.freeze({
|
|
2175
|
+
int: numberInput(true),
|
|
2176
|
+
float: numberInput(false),
|
|
2177
|
+
bool: (label, defaultValue = false, options) => {
|
|
2178
|
+
const key = resolveKey(label, options);
|
|
2179
|
+
registry?.push({ key, label, type: "boolean", default: defaultValue });
|
|
2180
|
+
return resolve(
|
|
2181
|
+
key,
|
|
2182
|
+
defaultValue,
|
|
2183
|
+
(raw) => typeof raw === "boolean" ? raw : raw === "true" ? true : raw === "false" ? false : void 0
|
|
2184
|
+
);
|
|
2185
|
+
},
|
|
2186
|
+
color: (label, defaultValue, options) => {
|
|
2187
|
+
const key = resolveKey(label, options);
|
|
2188
|
+
registry?.push({ key, label, type: "color", default: defaultValue });
|
|
2189
|
+
return resolve(key, defaultValue, (raw) => typeof raw === "string" ? raw : void 0);
|
|
2190
|
+
},
|
|
2191
|
+
string: (label, defaultValue = "", options) => {
|
|
2192
|
+
const key = resolveKey(label, options);
|
|
2193
|
+
registry?.push({ key, label, type: "string", default: defaultValue });
|
|
2194
|
+
return resolve(key, defaultValue, (raw) => typeof raw === "string" ? raw : String(raw));
|
|
2195
|
+
},
|
|
2196
|
+
select: selectInput,
|
|
2197
|
+
source: (label = "Source", defaultValue = "close", options) => selectInput(label, defaultValue, SCRIPT_SOURCE_INPUT_VALUES, options)
|
|
2198
|
+
});
|
|
2199
|
+
};
|
|
2124
2200
|
var SCRIPT_GUARD_BUDGET_MS = 1e3;
|
|
2125
2201
|
var ScriptBudgetError = class extends Error {
|
|
2126
2202
|
constructor() {
|
|
@@ -2395,31 +2471,34 @@ var injectLoopGuards = (source, guardName) => {
|
|
|
2395
2471
|
}
|
|
2396
2472
|
return out;
|
|
2397
2473
|
};
|
|
2398
|
-
var
|
|
2474
|
+
var compileScriptFactory = (source) => {
|
|
2399
2475
|
const makeFactory = (body) => new Function(
|
|
2400
2476
|
"__hpGuard",
|
|
2477
|
+
"input",
|
|
2401
2478
|
`"use strict";
|
|
2402
2479
|
${body}
|
|
2403
2480
|
;if (typeof compute !== "function") { throw new Error("Script must define function compute(bars, inputs, hp)"); }
|
|
2404
2481
|
return compute;`
|
|
2405
2482
|
);
|
|
2406
|
-
let factory;
|
|
2407
2483
|
try {
|
|
2408
|
-
|
|
2484
|
+
return makeFactory(injectLoopGuards(source, "__hpGuard"));
|
|
2409
2485
|
} catch {
|
|
2410
|
-
|
|
2486
|
+
return makeFactory(source);
|
|
2411
2487
|
}
|
|
2412
|
-
guard.reset();
|
|
2413
|
-
return factory(guard);
|
|
2414
2488
|
};
|
|
2415
|
-
var
|
|
2489
|
+
var extractScriptInputs = (source) => {
|
|
2490
|
+
const registry = [];
|
|
2416
2491
|
try {
|
|
2417
|
-
|
|
2418
|
-
|
|
2492
|
+
const factory = compileScriptFactory(source);
|
|
2493
|
+
const guard = createScriptGuard();
|
|
2494
|
+
guard.reset();
|
|
2495
|
+
factory(guard, createScriptInputHelper(null, registry));
|
|
2496
|
+
return { inputs: registry, error: null };
|
|
2419
2497
|
} catch (error) {
|
|
2420
|
-
return error instanceof Error ? error.message : String(error);
|
|
2498
|
+
return { inputs: registry, error: error instanceof Error ? error.message : String(error) };
|
|
2421
2499
|
}
|
|
2422
2500
|
};
|
|
2501
|
+
var validateScriptSource = (source) => extractScriptInputs(source).error;
|
|
2423
2502
|
var drawScriptError = (ctx, renderContext, name, message) => {
|
|
2424
2503
|
ctx.save();
|
|
2425
2504
|
ctx.font = "11px -apple-system, BlinkMacSystemFont, 'Segoe UI', sans-serif";
|
|
@@ -2436,18 +2515,24 @@ var compileScriptIndicator = (definition) => {
|
|
|
2436
2515
|
for (const input of definition.inputs ?? []) {
|
|
2437
2516
|
defaultInputs[input.key] = input.default;
|
|
2438
2517
|
}
|
|
2439
|
-
let
|
|
2518
|
+
let factory = null;
|
|
2440
2519
|
let compileError = null;
|
|
2441
2520
|
const guard = createScriptGuard();
|
|
2442
2521
|
try {
|
|
2443
|
-
|
|
2522
|
+
factory = compileScriptFactory(definition.source);
|
|
2523
|
+
const declared = [];
|
|
2524
|
+
guard.reset();
|
|
2525
|
+
factory(guard, createScriptInputHelper(null, declared));
|
|
2526
|
+
for (const input of declared) {
|
|
2527
|
+
defaultInputs[input.key] = input.default;
|
|
2528
|
+
}
|
|
2444
2529
|
} catch (error) {
|
|
2445
2530
|
compileError = error instanceof Error ? error.message : String(error);
|
|
2446
2531
|
}
|
|
2447
2532
|
const sourceKey = hashScriptSource(definition.source);
|
|
2448
2533
|
let trippedError = null;
|
|
2449
2534
|
const runCompute = (data, inputs) => {
|
|
2450
|
-
if (!
|
|
2535
|
+
if (!factory || compileError) {
|
|
2451
2536
|
return { error: compileError ?? "Script failed to compile" };
|
|
2452
2537
|
}
|
|
2453
2538
|
if (trippedError) {
|
|
@@ -2455,6 +2540,7 @@ var compileScriptIndicator = (definition) => {
|
|
|
2455
2540
|
}
|
|
2456
2541
|
try {
|
|
2457
2542
|
guard.reset();
|
|
2543
|
+
const compiled = factory(guard, createScriptInputHelper(inputs, null));
|
|
2458
2544
|
const result = compiled(data, inputs, SCRIPT_HELPERS);
|
|
2459
2545
|
if (!result || !Array.isArray(result.plots)) {
|
|
2460
2546
|
return { error: "compute() must return { plots: [...] }" };
|
|
@@ -2733,6 +2819,8 @@ var DEFAULT_OPTIONS = {
|
|
|
2733
2819
|
pinOutOfRangeLines: false,
|
|
2734
2820
|
doubleClickEnabled: true,
|
|
2735
2821
|
doubleClickAction: "reset",
|
|
2822
|
+
keyboard: { enabled: true, panBars: 1, deleteSelectedDrawing: true },
|
|
2823
|
+
accessibility: { label: "Price chart", description: "", focusable: true },
|
|
2736
2824
|
crosshair: DEFAULT_CROSSHAIR_OPTIONS,
|
|
2737
2825
|
grid: DEFAULT_GRID_OPTIONS,
|
|
2738
2826
|
watermark: DEFAULT_WATERMARK_OPTIONS,
|
|
@@ -2775,6 +2863,14 @@ var mergeChartOptions = (baseOptions, options = {}) => ({
|
|
|
2775
2863
|
...options.axisColor ? { lineColor: options.axisColor, textColor: options.axisColor } : {},
|
|
2776
2864
|
...options.yAxis ?? {}
|
|
2777
2865
|
},
|
|
2866
|
+
keyboard: {
|
|
2867
|
+
...baseOptions.keyboard,
|
|
2868
|
+
...options.keyboard ?? {}
|
|
2869
|
+
},
|
|
2870
|
+
accessibility: {
|
|
2871
|
+
...baseOptions.accessibility,
|
|
2872
|
+
...options.accessibility ?? {}
|
|
2873
|
+
},
|
|
2778
2874
|
crosshair: {
|
|
2779
2875
|
...baseOptions.crosshair,
|
|
2780
2876
|
...options.crosshair ?? {}
|
|
@@ -2948,7 +3044,29 @@ function createChart(element, options = {}) {
|
|
|
2948
3044
|
let drawingSelectHandler = null;
|
|
2949
3045
|
let drawingDoubleClickHandler = null;
|
|
2950
3046
|
let drawingEditTextHandler = null;
|
|
3047
|
+
let selectionChangeHandler = null;
|
|
3048
|
+
let contextMenuHandler = null;
|
|
3049
|
+
let keyboardShortcutHandler = null;
|
|
3050
|
+
let lastSelectionSignature = null;
|
|
2951
3051
|
let magnetMode = "none";
|
|
3052
|
+
let priceScaleMode = "linear";
|
|
3053
|
+
let priceScaleInverted = false;
|
|
3054
|
+
const PRICE_SCALE_LOG_FLOOR = 1e-9;
|
|
3055
|
+
const priceScaleTransform = (price) => priceScaleMode === "log" ? Math.log10(Math.max(PRICE_SCALE_LOG_FLOOR, price)) : price;
|
|
3056
|
+
const priceScaleUntransform = (value) => priceScaleMode === "log" ? Math.pow(10, value) : value;
|
|
3057
|
+
const percentBaselineForXStart = (xStartValue) => {
|
|
3058
|
+
if (priceScaleMode !== "percent") return null;
|
|
3059
|
+
const bar = data[clamp(Math.round(xStartValue), 0, Math.max(0, data.length - 1))];
|
|
3060
|
+
return bar && bar.c > 0 ? bar.c : null;
|
|
3061
|
+
};
|
|
3062
|
+
const formatPriceForScale = (price, percentBaseline) => {
|
|
3063
|
+
if (percentBaseline !== null) {
|
|
3064
|
+
const pct = (price / percentBaseline - 1) * 100;
|
|
3065
|
+
const rounded = Math.abs(pct) < 5e-3 ? 0 : pct;
|
|
3066
|
+
return `${rounded > 0 ? "+" : ""}${rounded.toFixed(2)}%`;
|
|
3067
|
+
}
|
|
3068
|
+
return formatPrice(price);
|
|
3069
|
+
};
|
|
2952
3070
|
let magnetModifierActive = false;
|
|
2953
3071
|
let shiftKeyActive = false;
|
|
2954
3072
|
const MAGNET_WEAK_THRESHOLD_PX = 16;
|
|
@@ -3092,6 +3210,39 @@ function createChart(element, options = {}) {
|
|
|
3092
3210
|
canvas.style.setProperty("-webkit-user-select", "none");
|
|
3093
3211
|
canvas.style.setProperty("-webkit-touch-callout", "none");
|
|
3094
3212
|
canvas.setAttribute("draggable", "false");
|
|
3213
|
+
const applyAccessibilityAttributes = () => {
|
|
3214
|
+
const a11y = mergedOptions.accessibility ?? {};
|
|
3215
|
+
const focusable = a11y.focusable !== false;
|
|
3216
|
+
if (focusable) {
|
|
3217
|
+
canvas.tabIndex = 0;
|
|
3218
|
+
} else {
|
|
3219
|
+
canvas.removeAttribute("tabindex");
|
|
3220
|
+
}
|
|
3221
|
+
canvas.setAttribute("role", "application");
|
|
3222
|
+
canvas.setAttribute("aria-label", a11y.label ?? "Price chart");
|
|
3223
|
+
if (a11y.description) {
|
|
3224
|
+
canvas.setAttribute("aria-description", a11y.description);
|
|
3225
|
+
} else {
|
|
3226
|
+
canvas.removeAttribute("aria-description");
|
|
3227
|
+
}
|
|
3228
|
+
if (focusable && (mergedOptions.keyboard?.enabled ?? true)) {
|
|
3229
|
+
canvas.setAttribute(
|
|
3230
|
+
"aria-keyshortcuts",
|
|
3231
|
+
"ArrowLeft ArrowRight ArrowUp ArrowDown Plus Minus Home End Delete Escape"
|
|
3232
|
+
);
|
|
3233
|
+
} else {
|
|
3234
|
+
canvas.removeAttribute("aria-keyshortcuts");
|
|
3235
|
+
}
|
|
3236
|
+
};
|
|
3237
|
+
applyAccessibilityAttributes();
|
|
3238
|
+
let focusFromPointer = false;
|
|
3239
|
+
canvas.addEventListener("focus", () => {
|
|
3240
|
+
canvas.style.outline = focusFromPointer ? "none" : "";
|
|
3241
|
+
focusFromPointer = false;
|
|
3242
|
+
});
|
|
3243
|
+
canvas.addEventListener("blur", () => {
|
|
3244
|
+
canvas.style.outline = "none";
|
|
3245
|
+
});
|
|
3095
3246
|
element.innerHTML = "";
|
|
3096
3247
|
element.appendChild(canvas);
|
|
3097
3248
|
const baseCanvas = document.createElement("canvas");
|
|
@@ -4282,9 +4433,14 @@ function createChart(element, options = {}) {
|
|
|
4282
4433
|
yMax
|
|
4283
4434
|
};
|
|
4284
4435
|
plotBottomForHit = fullChartBottom;
|
|
4436
|
+
const scaledYMin = priceScaleTransform(yMin);
|
|
4437
|
+
const scaledYRange = priceScaleTransform(yMax) - scaledYMin || 1;
|
|
4285
4438
|
const yFromPrice = (price) => {
|
|
4286
|
-
|
|
4439
|
+
const ratio = (priceScaleTransform(price) - scaledYMin) / scaledYRange;
|
|
4440
|
+
return priceScaleInverted ? chartTop + ratio * chartHeight : chartBottom - ratio * chartHeight;
|
|
4287
4441
|
};
|
|
4442
|
+
const percentBaseline = percentBaselineForXStart(xStart);
|
|
4443
|
+
const formatAxisPrice = (price) => formatPriceForScale(price, percentBaseline);
|
|
4288
4444
|
const xFromDrawingPoint = (point) => chartLeft + (point.index + 0.5 - xStart) / xSpan * chartWidth;
|
|
4289
4445
|
const FIB_RATIOS = [0, 0.236, 0.382, 0.5, 0.618, 0.786, 1, 1.618];
|
|
4290
4446
|
const FIB_EXT_RATIOS = [0, 0.382, 0.5, 0.618, 1, 1.272, 1.618, 2.618];
|
|
@@ -5191,19 +5347,40 @@ function createChart(element, options = {}) {
|
|
|
5191
5347
|
const yTicks = Math.max(1, Math.floor(yTickCountInput));
|
|
5192
5348
|
const gridColor = grid.color ?? mergedOptions.gridColor;
|
|
5193
5349
|
const priceTicks = [];
|
|
5194
|
-
{
|
|
5195
|
-
|
|
5196
|
-
|
|
5197
|
-
|
|
5198
|
-
|
|
5199
|
-
|
|
5200
|
-
|
|
5201
|
-
|
|
5202
|
-
|
|
5203
|
-
|
|
5350
|
+
const pickNiceStep = (targetStep) => {
|
|
5351
|
+
if (!(targetStep > 0) || !Number.isFinite(targetStep)) return 1;
|
|
5352
|
+
const base = Math.pow(10, Math.floor(Math.log10(targetStep)));
|
|
5353
|
+
const frac = targetStep / base;
|
|
5354
|
+
return (frac <= 1 ? 1 : frac <= 2 ? 2 : frac <= 2.5 ? 2.5 : frac <= 5 ? 5 : 10) * base;
|
|
5355
|
+
};
|
|
5356
|
+
if (percentBaseline !== null) {
|
|
5357
|
+
const pctMin = (yMin / percentBaseline - 1) * 100;
|
|
5358
|
+
const pctMax = (yMax / percentBaseline - 1) * 100;
|
|
5359
|
+
const step = pickNiceStep((pctMax - pctMin) / Math.max(1, yTicks));
|
|
5360
|
+
const firstTick = Math.ceil(pctMin / step) * step;
|
|
5361
|
+
for (let i = 0; ; i += 1) {
|
|
5362
|
+
const pct = firstTick + i * step;
|
|
5363
|
+
if (pct > pctMax + step * 1e-6) break;
|
|
5364
|
+
priceTicks.push(percentBaseline * (1 + pct / 100));
|
|
5365
|
+
if (priceTicks.length > 200) break;
|
|
5366
|
+
}
|
|
5367
|
+
} else if (priceScaleMode === "log" && yMin > 0 && yMax / yMin > 4) {
|
|
5368
|
+
const mantissas = Math.log10(yMax / yMin) > 8 ? [1] : [1, 2, 5];
|
|
5369
|
+
const firstDecade = Math.floor(Math.log10(Math.max(PRICE_SCALE_LOG_FLOOR, yMin)));
|
|
5370
|
+
const lastDecade = Math.ceil(Math.log10(yMax));
|
|
5371
|
+
for (let decade = firstDecade; decade <= lastDecade; decade += 1) {
|
|
5372
|
+
for (const mantissa of mantissas) {
|
|
5373
|
+
const price = mantissa * Math.pow(10, decade);
|
|
5374
|
+
if (price >= yMin && price <= yMax) priceTicks.push(price);
|
|
5375
|
+
if (priceTicks.length > 200) break;
|
|
5204
5376
|
}
|
|
5205
|
-
}
|
|
5206
|
-
|
|
5377
|
+
}
|
|
5378
|
+
} else {
|
|
5379
|
+
const targetStep = yRange / Math.max(1, yTicks);
|
|
5380
|
+
let step = pickNiceStep(targetStep);
|
|
5381
|
+
const instrumentTick = getConfiguredTickSize();
|
|
5382
|
+
if (targetStep > 0 && Number.isFinite(targetStep) && instrumentTick > 0 && step < instrumentTick * 1e6) {
|
|
5383
|
+
step = Math.max(instrumentTick, Math.round(step / instrumentTick) * instrumentTick);
|
|
5207
5384
|
}
|
|
5208
5385
|
const firstTick = Math.ceil(yMin / step) * step;
|
|
5209
5386
|
for (let i = 0; ; i += 1) {
|
|
@@ -5966,7 +6143,7 @@ function createChart(element, options = {}) {
|
|
|
5966
6143
|
ctx.font = `${yAxisFontSize}px ${mergedOptions.fontFamily}`;
|
|
5967
6144
|
for (const price of priceTicks) {
|
|
5968
6145
|
const y = clamp(yFromPrice(price), chartTop + priceScaleTickLabelInset, chartBottom - priceScaleTickLabelInset);
|
|
5969
|
-
drawText(
|
|
6146
|
+
drawText(formatAxisPrice(price), chartRight + 6, y, "left", "middle", yAxis.textColor);
|
|
5970
6147
|
}
|
|
5971
6148
|
ctx.font = prevFont;
|
|
5972
6149
|
}
|
|
@@ -6041,7 +6218,7 @@ function createChart(element, options = {}) {
|
|
|
6041
6218
|
...ticker.showCountdownInLabel ? [getCountdownText()] : []
|
|
6042
6219
|
].map((value) => value === null || value === void 0 ? "" : String(value).trim()).filter((value) => value.length > 0);
|
|
6043
6220
|
addPriceAxisLabel({
|
|
6044
|
-
text:
|
|
6221
|
+
text: formatAxisPrice(tickerPrice),
|
|
6045
6222
|
...tickerSubtexts.length > 0 ? { subtexts: tickerSubtexts } : {},
|
|
6046
6223
|
subtextColor: ticker.labelSubtextColor ?? ticker.labelTextColor ?? "#0b1220",
|
|
6047
6224
|
...ticker.labelSubtextFontSize === void 0 ? {} : { subtextFontSize: ticker.labelSubtextFontSize },
|
|
@@ -6068,7 +6245,7 @@ function createChart(element, options = {}) {
|
|
|
6068
6245
|
const previousClose = previousCloseCandidate;
|
|
6069
6246
|
drawReferenceLine(previousClose, labels.previousCloseColor, "dashed");
|
|
6070
6247
|
addPriceAxisLabel({
|
|
6071
|
-
text: `PDC ${
|
|
6248
|
+
text: `PDC ${formatAxisPrice(previousClose)}`,
|
|
6072
6249
|
price: previousClose,
|
|
6073
6250
|
backgroundColor: labels.backgroundColor,
|
|
6074
6251
|
textColor: labels.mutedTextColor,
|
|
@@ -6087,7 +6264,7 @@ function createChart(element, options = {}) {
|
|
|
6087
6264
|
if (point.l < visibleLow) visibleLow = point.l;
|
|
6088
6265
|
}
|
|
6089
6266
|
addPriceAxisLabel({
|
|
6090
|
-
text: `H ${
|
|
6267
|
+
text: `H ${formatAxisPrice(visibleHigh)}`,
|
|
6091
6268
|
price: visibleHigh,
|
|
6092
6269
|
backgroundColor: labels.backgroundColor,
|
|
6093
6270
|
textColor: labels.textColor,
|
|
@@ -6095,7 +6272,7 @@ function createChart(element, options = {}) {
|
|
|
6095
6272
|
priority: 40
|
|
6096
6273
|
});
|
|
6097
6274
|
addPriceAxisLabel({
|
|
6098
|
-
text: `L ${
|
|
6275
|
+
text: `L ${formatAxisPrice(visibleLow)}`,
|
|
6099
6276
|
price: visibleLow,
|
|
6100
6277
|
backgroundColor: labels.backgroundColor,
|
|
6101
6278
|
textColor: labels.textColor,
|
|
@@ -6107,7 +6284,7 @@ function createChart(element, options = {}) {
|
|
|
6107
6284
|
if (Number.isFinite(labels.bidPrice)) {
|
|
6108
6285
|
drawReferenceLine(labels.bidPrice, labels.bidColor, "dotted");
|
|
6109
6286
|
addPriceAxisLabel({
|
|
6110
|
-
text: `B ${
|
|
6287
|
+
text: `B ${formatAxisPrice(labels.bidPrice)}`,
|
|
6111
6288
|
price: labels.bidPrice,
|
|
6112
6289
|
backgroundColor: labels.bidColor,
|
|
6113
6290
|
textColor: "#0b1220",
|
|
@@ -6118,7 +6295,7 @@ function createChart(element, options = {}) {
|
|
|
6118
6295
|
if (Number.isFinite(labels.askPrice)) {
|
|
6119
6296
|
drawReferenceLine(labels.askPrice, labels.askColor, "dotted");
|
|
6120
6297
|
addPriceAxisLabel({
|
|
6121
|
-
text: `A ${
|
|
6298
|
+
text: `A ${formatAxisPrice(labels.askPrice)}`,
|
|
6122
6299
|
price: labels.askPrice,
|
|
6123
6300
|
backgroundColor: labels.askColor,
|
|
6124
6301
|
textColor: "#0b1220",
|
|
@@ -6293,6 +6470,7 @@ function createChart(element, options = {}) {
|
|
|
6293
6470
|
xSpan
|
|
6294
6471
|
};
|
|
6295
6472
|
paintCrosshairOverlay();
|
|
6473
|
+
emitSelectionChangeIfMoved();
|
|
6296
6474
|
};
|
|
6297
6475
|
const paintCrosshairOverlay = () => {
|
|
6298
6476
|
crosshairPriceActionRegion = null;
|
|
@@ -6379,8 +6557,11 @@ function createChart(element, options = {}) {
|
|
|
6379
6557
|
ctx.restore();
|
|
6380
6558
|
};
|
|
6381
6559
|
if (crosshair.showPriceLabel) {
|
|
6382
|
-
const
|
|
6383
|
-
const
|
|
6560
|
+
const scaledYMin = priceScaleTransform(yMin);
|
|
6561
|
+
const scaledYRange = priceScaleTransform(yMin + yRange) - scaledYMin || 1;
|
|
6562
|
+
const hoverRatio = priceScaleInverted ? (cy - chartTop) / chartHeight : (chartBottom - cy) / chartHeight;
|
|
6563
|
+
const hoverPrice = priceScaleUntransform(scaledYMin + hoverRatio * scaledYRange);
|
|
6564
|
+
const priceText = formatPriceForScale(hoverPrice, percentBaselineForXStart(xStart));
|
|
6384
6565
|
const priceWidth = getRightAxisLabelWidth(chartRight, getPriceLabelWidth(priceText, labelPaddingX));
|
|
6385
6566
|
const priceX = getRightAxisLabelX(chartRight);
|
|
6386
6567
|
const priceY = clamp(cy - labelHeight / 2, chartTop, chartBottom - labelHeight);
|
|
@@ -6548,8 +6729,8 @@ function createChart(element, options = {}) {
|
|
|
6548
6729
|
const maxRange = bounds.hardSpan;
|
|
6549
6730
|
const nextRange = clamp(currentRange * factor, minRange, maxRange);
|
|
6550
6731
|
const anchorRatio = clamp((anchorY - drawState.chartTop) / drawState.chartHeight, 0, 1);
|
|
6551
|
-
const anchorPrice = currentMax - anchorRatio * currentRange;
|
|
6552
|
-
const nextMax = anchorPrice + anchorRatio * nextRange;
|
|
6732
|
+
const anchorPrice = priceScaleInverted ? currentMin + anchorRatio * currentRange : currentMax - anchorRatio * currentRange;
|
|
6733
|
+
const nextMax = priceScaleInverted ? anchorPrice + (1 - anchorRatio) * nextRange : anchorPrice + anchorRatio * nextRange;
|
|
6553
6734
|
const nextMin = nextMax - nextRange;
|
|
6554
6735
|
const clamped = clampYRange(nextMin, nextMax);
|
|
6555
6736
|
yMinOverride = clamped.min;
|
|
@@ -6573,7 +6754,7 @@ function createChart(element, options = {}) {
|
|
|
6573
6754
|
const currentMin = yMinOverride ?? drawState.yMin;
|
|
6574
6755
|
const currentMax = yMaxOverride ?? drawState.yMax;
|
|
6575
6756
|
const range = currentMax - currentMin || 1;
|
|
6576
|
-
const priceShift = deltaY / drawState.chartHeight * range;
|
|
6757
|
+
const priceShift = deltaY / drawState.chartHeight * range * (priceScaleInverted ? -1 : 1);
|
|
6577
6758
|
const clamped = clampYRange(currentMin + priceShift, currentMax + priceShift);
|
|
6578
6759
|
yMinOverride = clamped.min;
|
|
6579
6760
|
yMaxOverride = clamped.max;
|
|
@@ -6806,8 +6987,14 @@ function createChart(element, options = {}) {
|
|
|
6806
6987
|
if (!drawState) {
|
|
6807
6988
|
return 0;
|
|
6808
6989
|
}
|
|
6809
|
-
const ratio = clamp(
|
|
6810
|
-
|
|
6990
|
+
const ratio = clamp(
|
|
6991
|
+
priceScaleInverted ? (y - drawState.chartTop) / drawState.chartHeight : (drawState.chartBottom - y) / drawState.chartHeight,
|
|
6992
|
+
0,
|
|
6993
|
+
1
|
|
6994
|
+
);
|
|
6995
|
+
const scaledMin = priceScaleTransform(drawState.yMin);
|
|
6996
|
+
const scaledMax = priceScaleTransform(drawState.yMax);
|
|
6997
|
+
return priceScaleUntransform(scaledMin + ratio * (scaledMax - scaledMin));
|
|
6811
6998
|
};
|
|
6812
6999
|
const applyMagnet = (y, index, price) => {
|
|
6813
7000
|
const effective = magnetModifierActive ? "strong" : magnetMode;
|
|
@@ -6879,8 +7066,102 @@ function createChart(element, options = {}) {
|
|
|
6879
7066
|
};
|
|
6880
7067
|
const canvasYFromDrawingPrice = (price) => {
|
|
6881
7068
|
if (!drawState) return 0;
|
|
6882
|
-
const
|
|
6883
|
-
|
|
7069
|
+
const scaledMin = priceScaleTransform(drawState.yMin);
|
|
7070
|
+
const scaledRange = priceScaleTransform(drawState.yMax) - scaledMin || 1;
|
|
7071
|
+
const ratio = (priceScaleTransform(price) - scaledMin) / scaledRange;
|
|
7072
|
+
return priceScaleInverted ? drawState.chartTop + ratio * drawState.chartHeight : drawState.chartBottom - ratio * drawState.chartHeight;
|
|
7073
|
+
};
|
|
7074
|
+
const getDrawingBounds = (drawing) => {
|
|
7075
|
+
if (!drawState || drawing.points.length === 0) {
|
|
7076
|
+
return null;
|
|
7077
|
+
}
|
|
7078
|
+
const { chartLeft, chartRight, chartTop, chartBottom } = drawState;
|
|
7079
|
+
let left;
|
|
7080
|
+
let right;
|
|
7081
|
+
let top;
|
|
7082
|
+
let bottom;
|
|
7083
|
+
if (drawing.type === "horizontal-line") {
|
|
7084
|
+
const y = canvasYFromDrawingPrice(drawing.points[0].price);
|
|
7085
|
+
left = chartLeft;
|
|
7086
|
+
right = chartRight;
|
|
7087
|
+
top = y;
|
|
7088
|
+
bottom = y;
|
|
7089
|
+
} else if (drawing.type === "vertical-line") {
|
|
7090
|
+
const x = canvasXFromDrawingPoint(drawing.points[0]);
|
|
7091
|
+
left = x;
|
|
7092
|
+
right = x;
|
|
7093
|
+
top = chartTop;
|
|
7094
|
+
bottom = chartBottom;
|
|
7095
|
+
} else {
|
|
7096
|
+
const xs = drawing.points.map((point) => canvasXFromDrawingPoint(point));
|
|
7097
|
+
const ys = drawing.points.map((point) => canvasYFromDrawingPrice(point.price));
|
|
7098
|
+
left = Math.min(...xs);
|
|
7099
|
+
right = Math.max(...xs);
|
|
7100
|
+
top = Math.min(...ys);
|
|
7101
|
+
bottom = Math.max(...ys);
|
|
7102
|
+
if (drawing.type === "ray") {
|
|
7103
|
+
right = chartRight;
|
|
7104
|
+
}
|
|
7105
|
+
}
|
|
7106
|
+
const clampX = (value) => clamp(value, chartLeft, chartRight);
|
|
7107
|
+
const clampY = (value) => clamp(value, chartTop, chartBottom);
|
|
7108
|
+
left = clampX(left);
|
|
7109
|
+
right = clampX(right);
|
|
7110
|
+
top = clampY(top);
|
|
7111
|
+
bottom = clampY(bottom);
|
|
7112
|
+
return {
|
|
7113
|
+
left,
|
|
7114
|
+
top,
|
|
7115
|
+
right,
|
|
7116
|
+
bottom,
|
|
7117
|
+
centerX: (left + right) / 2,
|
|
7118
|
+
centerY: (top + bottom) / 2
|
|
7119
|
+
};
|
|
7120
|
+
};
|
|
7121
|
+
const getSelectedDrawingWithBounds = () => {
|
|
7122
|
+
if (!selectedDrawingId) {
|
|
7123
|
+
return null;
|
|
7124
|
+
}
|
|
7125
|
+
const drawing = drawings.find((entry) => entry.id === selectedDrawingId);
|
|
7126
|
+
if (!drawing || !drawing.visible) {
|
|
7127
|
+
return null;
|
|
7128
|
+
}
|
|
7129
|
+
const bounds = getDrawingBounds(drawing);
|
|
7130
|
+
return bounds ? { drawing: serializeDrawing(drawing), bounds } : null;
|
|
7131
|
+
};
|
|
7132
|
+
const emitSelectionChangeIfMoved = () => {
|
|
7133
|
+
if (!selectionChangeHandler) {
|
|
7134
|
+
lastSelectionSignature = null;
|
|
7135
|
+
return;
|
|
7136
|
+
}
|
|
7137
|
+
const selection = getSelectedDrawingWithBounds();
|
|
7138
|
+
if (!selection || !drawState) {
|
|
7139
|
+
if (lastSelectionSignature !== null) {
|
|
7140
|
+
lastSelectionSignature = null;
|
|
7141
|
+
selectionChangeHandler(null);
|
|
7142
|
+
}
|
|
7143
|
+
return;
|
|
7144
|
+
}
|
|
7145
|
+
const dragging = drawingDragState !== null;
|
|
7146
|
+
const { bounds } = selection;
|
|
7147
|
+
const signature = `${selection.drawing.id}:${Math.round(bounds.left)}:${Math.round(bounds.top)}:${Math.round(
|
|
7148
|
+
bounds.right
|
|
7149
|
+
)}:${Math.round(bounds.bottom)}:${dragging ? 1 : 0}`;
|
|
7150
|
+
if (signature === lastSelectionSignature) {
|
|
7151
|
+
return;
|
|
7152
|
+
}
|
|
7153
|
+
lastSelectionSignature = signature;
|
|
7154
|
+
selectionChangeHandler({
|
|
7155
|
+
drawing: selection.drawing,
|
|
7156
|
+
bounds,
|
|
7157
|
+
plot: {
|
|
7158
|
+
left: drawState.chartLeft,
|
|
7159
|
+
top: drawState.chartTop,
|
|
7160
|
+
right: drawState.chartRight,
|
|
7161
|
+
bottom: drawState.chartBottom
|
|
7162
|
+
},
|
|
7163
|
+
dragging
|
|
7164
|
+
});
|
|
6884
7165
|
};
|
|
6885
7166
|
const distanceToSegment = (x, y, x1, y1, x2, y2) => {
|
|
6886
7167
|
const dx = x2 - x1;
|
|
@@ -7848,6 +8129,10 @@ function createChart(element, options = {}) {
|
|
|
7848
8129
|
if (event.pointerType === "touch" || event.pointerType === "pen") {
|
|
7849
8130
|
event.preventDefault();
|
|
7850
8131
|
}
|
|
8132
|
+
if (mergedOptions.accessibility?.focusable !== false && document.activeElement !== canvas) {
|
|
8133
|
+
focusFromPointer = true;
|
|
8134
|
+
canvas.focus({ preventScroll: true });
|
|
8135
|
+
}
|
|
7851
8136
|
cancelKineticPan();
|
|
7852
8137
|
panVelocitySamples.length = 0;
|
|
7853
8138
|
magnetModifierActive = event.metaKey || event.ctrlKey;
|
|
@@ -7965,12 +8250,14 @@ function createChart(element, options = {}) {
|
|
|
7965
8250
|
const drawingHit = getDrawingHit(point.x, point.y);
|
|
7966
8251
|
if (drawingHit) {
|
|
7967
8252
|
selectedDrawingId = drawingHit.drawing.id;
|
|
8253
|
+
const selectBounds = getDrawingBounds(drawingHit.drawing);
|
|
7968
8254
|
drawingSelectHandler?.({
|
|
7969
8255
|
drawing: serializeDrawing(drawingHit.drawing),
|
|
7970
8256
|
target: drawingHit.target,
|
|
7971
8257
|
...drawingHit.pointIndex === void 0 ? {} : { pointIndex: drawingHit.pointIndex },
|
|
7972
8258
|
x: point.x,
|
|
7973
|
-
y: point.y
|
|
8259
|
+
y: point.y,
|
|
8260
|
+
...selectBounds ? { bounds: selectBounds } : {}
|
|
7974
8261
|
});
|
|
7975
8262
|
if (!drawingHit.drawing.locked) {
|
|
7976
8263
|
const startCanvasPoint = drawingPointFromCanvas(point.x, point.y, false);
|
|
@@ -8531,7 +8818,152 @@ function createChart(element, options = {}) {
|
|
|
8531
8818
|
};
|
|
8532
8819
|
const onContextMenu = (event) => {
|
|
8533
8820
|
event.preventDefault();
|
|
8821
|
+
if (!contextMenuHandler || !drawState) {
|
|
8822
|
+
return;
|
|
8823
|
+
}
|
|
8824
|
+
const point = getCanvasPoint(event);
|
|
8825
|
+
const hitRegion = getHitRegion(point.x, point.y);
|
|
8826
|
+
if (hitRegion === "outside") {
|
|
8827
|
+
return;
|
|
8828
|
+
}
|
|
8829
|
+
const pane = hitRegion === "plot" ? getPaneAt(point.x, point.y) : null;
|
|
8830
|
+
const drawingHit = hitRegion === "plot" && !pane ? getDrawingHit(point.x, point.y) : null;
|
|
8831
|
+
if (drawingHit) {
|
|
8832
|
+
if (selectedDrawingId !== drawingHit.drawing.id) {
|
|
8833
|
+
selectedDrawingId = drawingHit.drawing.id;
|
|
8834
|
+
scheduleDraw();
|
|
8835
|
+
}
|
|
8836
|
+
}
|
|
8837
|
+
const region = drawingHit ? "drawing" : pane ? "indicator-pane" : hitRegion;
|
|
8838
|
+
const index = indexFromCanvasX(point.x);
|
|
8839
|
+
const barTime = index === null ? null : getTimeForIndex(index);
|
|
8840
|
+
const bar = index === null ? void 0 : data[index];
|
|
8841
|
+
const inMainPane = !pane && (hitRegion === "plot" || hitRegion === "y-axis");
|
|
8842
|
+
contextMenuHandler({
|
|
8843
|
+
region,
|
|
8844
|
+
x: point.x,
|
|
8845
|
+
y: point.y,
|
|
8846
|
+
clientX: event.clientX,
|
|
8847
|
+
clientY: event.clientY,
|
|
8848
|
+
...inMainPane ? { price: roundToPricePrecision(priceFromCanvasY(point.y)) } : {},
|
|
8849
|
+
...index === null ? {} : { index },
|
|
8850
|
+
...barTime ? { time: barTime.toISOString() } : {},
|
|
8851
|
+
...bar ? {
|
|
8852
|
+
point: {
|
|
8853
|
+
t: bar.time.toISOString(),
|
|
8854
|
+
o: bar.o,
|
|
8855
|
+
h: bar.h,
|
|
8856
|
+
l: bar.l,
|
|
8857
|
+
c: bar.c,
|
|
8858
|
+
...bar.v === void 0 ? {} : { v: bar.v }
|
|
8859
|
+
}
|
|
8860
|
+
} : {},
|
|
8861
|
+
...drawingHit ? {
|
|
8862
|
+
drawing: serializeDrawing(drawingHit.drawing),
|
|
8863
|
+
drawingTarget: drawingHit.target,
|
|
8864
|
+
...drawingHit.pointIndex === void 0 ? {} : { pointIndex: drawingHit.pointIndex }
|
|
8865
|
+
} : {},
|
|
8866
|
+
...pane ? { indicator: { id: pane.id, type: pane.type } } : {}
|
|
8867
|
+
});
|
|
8534
8868
|
};
|
|
8869
|
+
const onCanvasKeyDown = (event) => {
|
|
8870
|
+
const keyboard = mergedOptions.keyboard ?? {};
|
|
8871
|
+
if (keyboard.enabled === false) {
|
|
8872
|
+
return;
|
|
8873
|
+
}
|
|
8874
|
+
if (event.altKey || event.ctrlKey || event.metaKey) {
|
|
8875
|
+
return;
|
|
8876
|
+
}
|
|
8877
|
+
const panStep = Math.max(1, keyboard.panBars ?? 1) * (event.shiftKey ? 10 : 1);
|
|
8878
|
+
const emit = (action, drawing) => {
|
|
8879
|
+
keyboardShortcutHandler?.({
|
|
8880
|
+
action,
|
|
8881
|
+
key: event.key,
|
|
8882
|
+
shiftKey: event.shiftKey,
|
|
8883
|
+
...drawing ? { drawing } : {}
|
|
8884
|
+
});
|
|
8885
|
+
};
|
|
8886
|
+
switch (event.key) {
|
|
8887
|
+
case "Delete":
|
|
8888
|
+
case "Backspace": {
|
|
8889
|
+
if (keyboard.deleteSelectedDrawing === false || !selectedDrawingId) {
|
|
8890
|
+
return;
|
|
8891
|
+
}
|
|
8892
|
+
const target = drawings.find((entry) => entry.id === selectedDrawingId);
|
|
8893
|
+
if (!target) {
|
|
8894
|
+
return;
|
|
8895
|
+
}
|
|
8896
|
+
const removed = serializeDrawing(target);
|
|
8897
|
+
removeDrawing(selectedDrawingId);
|
|
8898
|
+
selectedDrawingId = null;
|
|
8899
|
+
scheduleDraw();
|
|
8900
|
+
emit("delete-drawing", removed);
|
|
8901
|
+
break;
|
|
8902
|
+
}
|
|
8903
|
+
case "Escape": {
|
|
8904
|
+
if (cancelDrawing()) {
|
|
8905
|
+
emit("cancel");
|
|
8906
|
+
break;
|
|
8907
|
+
}
|
|
8908
|
+
if (activeDrawingTool) {
|
|
8909
|
+
setActiveDrawingTool(null);
|
|
8910
|
+
emit("cancel");
|
|
8911
|
+
break;
|
|
8912
|
+
}
|
|
8913
|
+
if (selectedDrawingId) {
|
|
8914
|
+
setSelectedDrawing(null);
|
|
8915
|
+
emit("cancel");
|
|
8916
|
+
break;
|
|
8917
|
+
}
|
|
8918
|
+
return;
|
|
8919
|
+
}
|
|
8920
|
+
case "ArrowLeft":
|
|
8921
|
+
panX(-panStep);
|
|
8922
|
+
emit("pan-left");
|
|
8923
|
+
break;
|
|
8924
|
+
case "ArrowRight":
|
|
8925
|
+
panX(panStep);
|
|
8926
|
+
emit("pan-right");
|
|
8927
|
+
break;
|
|
8928
|
+
case "ArrowUp":
|
|
8929
|
+
case "ArrowDown": {
|
|
8930
|
+
if (!drawState) {
|
|
8931
|
+
return;
|
|
8932
|
+
}
|
|
8933
|
+
const step = (drawState.yMax - drawState.yMin) / 20 * (event.shiftKey ? 5 : 1);
|
|
8934
|
+
panY(event.key === "ArrowUp" ? step : -step);
|
|
8935
|
+
emit(event.key === "ArrowUp" ? "pan-up" : "pan-down");
|
|
8936
|
+
break;
|
|
8937
|
+
}
|
|
8938
|
+
case "+":
|
|
8939
|
+
case "=":
|
|
8940
|
+
zoomInX();
|
|
8941
|
+
emit("zoom-in");
|
|
8942
|
+
break;
|
|
8943
|
+
case "-":
|
|
8944
|
+
case "_":
|
|
8945
|
+
zoomOutX();
|
|
8946
|
+
emit("zoom-out");
|
|
8947
|
+
break;
|
|
8948
|
+
case "Home":
|
|
8949
|
+
fitContent();
|
|
8950
|
+
emit("reset-viewport");
|
|
8951
|
+
break;
|
|
8952
|
+
case "End":
|
|
8953
|
+
setFollowingLatest(true);
|
|
8954
|
+
emit("scroll-to-realtime");
|
|
8955
|
+
break;
|
|
8956
|
+
case "r":
|
|
8957
|
+
case "R":
|
|
8958
|
+
resetViewport();
|
|
8959
|
+
emit("reset-viewport");
|
|
8960
|
+
break;
|
|
8961
|
+
default:
|
|
8962
|
+
return;
|
|
8963
|
+
}
|
|
8964
|
+
event.preventDefault();
|
|
8965
|
+
};
|
|
8966
|
+
canvas.addEventListener("keydown", onCanvasKeyDown);
|
|
8535
8967
|
canvas.addEventListener("pointerdown", onPointerDown);
|
|
8536
8968
|
canvas.addEventListener("pointermove", onPointerMove);
|
|
8537
8969
|
canvas.addEventListener("pointerup", endPointerDrag);
|
|
@@ -8578,6 +9010,7 @@ function createChart(element, options = {}) {
|
|
|
8578
9010
|
resetRightMarginCache();
|
|
8579
9011
|
doubleClickEnabled = mergedOptions.doubleClickEnabled;
|
|
8580
9012
|
doubleClickAction = mergedOptions.doubleClickAction;
|
|
9013
|
+
applyAccessibilityAttributes();
|
|
8581
9014
|
const isTickerSmoothingEnabled = mergedOptions.tickerLine?.smoothing ?? false;
|
|
8582
9015
|
if (!isTickerSmoothingEnabled) {
|
|
8583
9016
|
smoothedTickerPrice = null;
|
|
@@ -8894,6 +9327,16 @@ function createChart(element, options = {}) {
|
|
|
8894
9327
|
magnetMode = mode;
|
|
8895
9328
|
};
|
|
8896
9329
|
const getMagnetMode = () => magnetMode;
|
|
9330
|
+
const setPriceScale = (options2) => {
|
|
9331
|
+
if (options2.mode === "linear" || options2.mode === "log" || options2.mode === "percent") {
|
|
9332
|
+
priceScaleMode = options2.mode;
|
|
9333
|
+
}
|
|
9334
|
+
if (typeof options2.inverted === "boolean") {
|
|
9335
|
+
priceScaleInverted = options2.inverted;
|
|
9336
|
+
}
|
|
9337
|
+
scheduleDraw();
|
|
9338
|
+
};
|
|
9339
|
+
const getPriceScale = () => ({ mode: priceScaleMode, inverted: priceScaleInverted });
|
|
8897
9340
|
const getDrawings = () => drawings.map((drawing) => serializeDrawing(drawing));
|
|
8898
9341
|
const setDrawings = (nextDrawings) => {
|
|
8899
9342
|
drawings = dedupeDrawingIds(nextDrawings.map((drawing) => normalizeDrawingState(drawing)));
|
|
@@ -8951,6 +9394,23 @@ function createChart(element, options = {}) {
|
|
|
8951
9394
|
const onDrawingHover = (handler) => {
|
|
8952
9395
|
drawingHoverHandler = handler;
|
|
8953
9396
|
};
|
|
9397
|
+
const onSelectionChange = (handler) => {
|
|
9398
|
+
selectionChangeHandler = handler;
|
|
9399
|
+
lastSelectionSignature = null;
|
|
9400
|
+
if (handler) {
|
|
9401
|
+
emitSelectionChangeIfMoved();
|
|
9402
|
+
}
|
|
9403
|
+
};
|
|
9404
|
+
const getSelectedDrawing = () => getSelectedDrawingWithBounds();
|
|
9405
|
+
const onContextMenuHandler = (handler) => {
|
|
9406
|
+
contextMenuHandler = handler;
|
|
9407
|
+
};
|
|
9408
|
+
const onKeyboardShortcut = (handler) => {
|
|
9409
|
+
keyboardShortcutHandler = handler;
|
|
9410
|
+
};
|
|
9411
|
+
const focus = () => {
|
|
9412
|
+
canvas.focus({ preventScroll: true });
|
|
9413
|
+
};
|
|
8954
9414
|
const saveState = () => {
|
|
8955
9415
|
const drawingDefaults = {};
|
|
8956
9416
|
for (const [tool, defaults] of drawingToolDefaults) {
|
|
@@ -8963,6 +9423,7 @@ function createChart(element, options = {}) {
|
|
|
8963
9423
|
drawings: getDrawings(),
|
|
8964
9424
|
indicators: getIndicators(),
|
|
8965
9425
|
magnetMode: getMagnetMode(),
|
|
9426
|
+
priceScale: getPriceScale(),
|
|
8966
9427
|
drawingDefaults
|
|
8967
9428
|
};
|
|
8968
9429
|
};
|
|
@@ -8982,6 +9443,9 @@ function createChart(element, options = {}) {
|
|
|
8982
9443
|
if (state.magnetMode === "none" || state.magnetMode === "weak" || state.magnetMode === "strong") {
|
|
8983
9444
|
setMagnetMode(state.magnetMode);
|
|
8984
9445
|
}
|
|
9446
|
+
if (state.priceScale && typeof state.priceScale === "object") {
|
|
9447
|
+
setPriceScale(state.priceScale);
|
|
9448
|
+
}
|
|
8985
9449
|
if (state.drawingDefaults && typeof state.drawingDefaults === "object") {
|
|
8986
9450
|
for (const [tool, defaults] of Object.entries(state.drawingDefaults)) {
|
|
8987
9451
|
if (defaults && typeof defaults === "object") {
|
|
@@ -9011,6 +9475,7 @@ function createChart(element, options = {}) {
|
|
|
9011
9475
|
drawRafId = null;
|
|
9012
9476
|
pendingDrawUpdateAutoScale = false;
|
|
9013
9477
|
}
|
|
9478
|
+
canvas.removeEventListener("keydown", onCanvasKeyDown);
|
|
9014
9479
|
canvas.removeEventListener("pointerdown", onPointerDown);
|
|
9015
9480
|
canvas.removeEventListener("pointermove", onPointerMove);
|
|
9016
9481
|
canvas.removeEventListener("pointerup", endPointerDrag);
|
|
@@ -9074,8 +9539,15 @@ function createChart(element, options = {}) {
|
|
|
9074
9539
|
onDrawingSelect,
|
|
9075
9540
|
onDrawingDoubleClick,
|
|
9076
9541
|
onDrawingEditText,
|
|
9542
|
+
onSelectionChange,
|
|
9543
|
+
getSelectedDrawing,
|
|
9544
|
+
onContextMenu: onContextMenuHandler,
|
|
9545
|
+
onKeyboardShortcut,
|
|
9546
|
+
focus,
|
|
9077
9547
|
setMagnetMode,
|
|
9078
9548
|
getMagnetMode,
|
|
9549
|
+
setPriceScale,
|
|
9550
|
+
getPriceScale,
|
|
9079
9551
|
cancelDrawing,
|
|
9080
9552
|
setTradeMarkers,
|
|
9081
9553
|
setSelectedDrawing,
|
|
@@ -9101,7 +9573,9 @@ function createChart(element, options = {}) {
|
|
|
9101
9573
|
export {
|
|
9102
9574
|
FIB_DEFAULT_PALETTE,
|
|
9103
9575
|
POSITION_DEFAULT_COLORS,
|
|
9576
|
+
SCRIPT_SOURCE_INPUT_VALUES,
|
|
9104
9577
|
compileScriptIndicator,
|
|
9105
9578
|
createChart,
|
|
9579
|
+
extractScriptInputs,
|
|
9106
9580
|
validateScriptSource
|
|
9107
9581
|
};
|