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
|
@@ -22,8 +22,10 @@ var index_exports = {};
|
|
|
22
22
|
__export(index_exports, {
|
|
23
23
|
FIB_DEFAULT_PALETTE: () => FIB_DEFAULT_PALETTE,
|
|
24
24
|
POSITION_DEFAULT_COLORS: () => POSITION_DEFAULT_COLORS,
|
|
25
|
+
SCRIPT_SOURCE_INPUT_VALUES: () => SCRIPT_SOURCE_INPUT_VALUES,
|
|
25
26
|
compileScriptIndicator: () => compileScriptIndicator,
|
|
26
27
|
createChart: () => createChart,
|
|
28
|
+
extractScriptInputs: () => extractScriptInputs,
|
|
27
29
|
validateScriptSource: () => validateScriptSource
|
|
28
30
|
});
|
|
29
31
|
module.exports = __toCommonJS(index_exports);
|
|
@@ -2151,6 +2153,82 @@ var hashScriptSource = (source) => {
|
|
|
2151
2153
|
}
|
|
2152
2154
|
return (hash >>> 0).toString(36);
|
|
2153
2155
|
};
|
|
2156
|
+
var SCRIPT_SOURCE_INPUT_VALUES = ["open", "high", "low", "close", "hl2", "hlc3", "ohlc4"];
|
|
2157
|
+
var slugifyInputLabel = (label) => {
|
|
2158
|
+
const slug = String(label).trim().toLowerCase().replace(/[^a-z0-9]+/g, "_").replace(/^_+|_+$/g, "");
|
|
2159
|
+
return slug || "input";
|
|
2160
|
+
};
|
|
2161
|
+
var createScriptInputHelper = (values, registry) => {
|
|
2162
|
+
const usedKeys = /* @__PURE__ */ new Set();
|
|
2163
|
+
const resolveKey = (label, options) => {
|
|
2164
|
+
let key = options?.key ?? slugifyInputLabel(label);
|
|
2165
|
+
if (usedKeys.has(key)) {
|
|
2166
|
+
let suffix = 2;
|
|
2167
|
+
while (usedKeys.has(`${key}_${suffix}`)) suffix += 1;
|
|
2168
|
+
key = `${key}_${suffix}`;
|
|
2169
|
+
}
|
|
2170
|
+
usedKeys.add(key);
|
|
2171
|
+
return key;
|
|
2172
|
+
};
|
|
2173
|
+
const resolve = (key, fallback, coerce) => {
|
|
2174
|
+
if (!values) return fallback;
|
|
2175
|
+
const raw = values[key];
|
|
2176
|
+
if (raw === void 0 || raw === null) return fallback;
|
|
2177
|
+
const coerced = coerce(raw);
|
|
2178
|
+
return coerced === void 0 ? fallback : coerced;
|
|
2179
|
+
};
|
|
2180
|
+
const coerceNumber = (raw) => {
|
|
2181
|
+
const num = typeof raw === "number" ? raw : Number(raw);
|
|
2182
|
+
return Number.isFinite(num) ? num : void 0;
|
|
2183
|
+
};
|
|
2184
|
+
const numberInput = (integer) => (label, defaultValue, options) => {
|
|
2185
|
+
const key = resolveKey(label, options);
|
|
2186
|
+
registry?.push({
|
|
2187
|
+
key,
|
|
2188
|
+
label,
|
|
2189
|
+
type: "number",
|
|
2190
|
+
default: defaultValue,
|
|
2191
|
+
...options?.min !== void 0 ? { min: options.min } : {},
|
|
2192
|
+
...options?.max !== void 0 ? { max: options.max } : {},
|
|
2193
|
+
...options?.step !== void 0 ? { step: options.step } : integer ? { step: 1 } : {}
|
|
2194
|
+
});
|
|
2195
|
+
const value = resolve(key, defaultValue, coerceNumber);
|
|
2196
|
+
return integer ? Math.round(value) : value;
|
|
2197
|
+
};
|
|
2198
|
+
const selectInput = (label, defaultValue, choices, options) => {
|
|
2199
|
+
const key = resolveKey(label, options);
|
|
2200
|
+
const normalized = (Array.isArray(choices) ? choices : []).map(
|
|
2201
|
+
(choice) => typeof choice === "string" ? { value: choice, label: choice } : { value: String(choice.value), label: String(choice.label ?? choice.value) }
|
|
2202
|
+
);
|
|
2203
|
+
registry?.push({ key, label, type: "select", default: defaultValue, options: normalized });
|
|
2204
|
+
return resolve(key, defaultValue, (raw) => typeof raw === "string" ? raw : String(raw));
|
|
2205
|
+
};
|
|
2206
|
+
return Object.freeze({
|
|
2207
|
+
int: numberInput(true),
|
|
2208
|
+
float: numberInput(false),
|
|
2209
|
+
bool: (label, defaultValue = false, options) => {
|
|
2210
|
+
const key = resolveKey(label, options);
|
|
2211
|
+
registry?.push({ key, label, type: "boolean", default: defaultValue });
|
|
2212
|
+
return resolve(
|
|
2213
|
+
key,
|
|
2214
|
+
defaultValue,
|
|
2215
|
+
(raw) => typeof raw === "boolean" ? raw : raw === "true" ? true : raw === "false" ? false : void 0
|
|
2216
|
+
);
|
|
2217
|
+
},
|
|
2218
|
+
color: (label, defaultValue, options) => {
|
|
2219
|
+
const key = resolveKey(label, options);
|
|
2220
|
+
registry?.push({ key, label, type: "color", default: defaultValue });
|
|
2221
|
+
return resolve(key, defaultValue, (raw) => typeof raw === "string" ? raw : void 0);
|
|
2222
|
+
},
|
|
2223
|
+
string: (label, defaultValue = "", options) => {
|
|
2224
|
+
const key = resolveKey(label, options);
|
|
2225
|
+
registry?.push({ key, label, type: "string", default: defaultValue });
|
|
2226
|
+
return resolve(key, defaultValue, (raw) => typeof raw === "string" ? raw : String(raw));
|
|
2227
|
+
},
|
|
2228
|
+
select: selectInput,
|
|
2229
|
+
source: (label = "Source", defaultValue = "close", options) => selectInput(label, defaultValue, SCRIPT_SOURCE_INPUT_VALUES, options)
|
|
2230
|
+
});
|
|
2231
|
+
};
|
|
2154
2232
|
var SCRIPT_GUARD_BUDGET_MS = 1e3;
|
|
2155
2233
|
var ScriptBudgetError = class extends Error {
|
|
2156
2234
|
constructor() {
|
|
@@ -2425,31 +2503,34 @@ var injectLoopGuards = (source, guardName) => {
|
|
|
2425
2503
|
}
|
|
2426
2504
|
return out;
|
|
2427
2505
|
};
|
|
2428
|
-
var
|
|
2506
|
+
var compileScriptFactory = (source) => {
|
|
2429
2507
|
const makeFactory = (body) => new Function(
|
|
2430
2508
|
"__hpGuard",
|
|
2509
|
+
"input",
|
|
2431
2510
|
`"use strict";
|
|
2432
2511
|
${body}
|
|
2433
2512
|
;if (typeof compute !== "function") { throw new Error("Script must define function compute(bars, inputs, hp)"); }
|
|
2434
2513
|
return compute;`
|
|
2435
2514
|
);
|
|
2436
|
-
let factory;
|
|
2437
2515
|
try {
|
|
2438
|
-
|
|
2516
|
+
return makeFactory(injectLoopGuards(source, "__hpGuard"));
|
|
2439
2517
|
} catch {
|
|
2440
|
-
|
|
2518
|
+
return makeFactory(source);
|
|
2441
2519
|
}
|
|
2442
|
-
guard.reset();
|
|
2443
|
-
return factory(guard);
|
|
2444
2520
|
};
|
|
2445
|
-
var
|
|
2521
|
+
var extractScriptInputs = (source) => {
|
|
2522
|
+
const registry = [];
|
|
2446
2523
|
try {
|
|
2447
|
-
|
|
2448
|
-
|
|
2524
|
+
const factory = compileScriptFactory(source);
|
|
2525
|
+
const guard = createScriptGuard();
|
|
2526
|
+
guard.reset();
|
|
2527
|
+
factory(guard, createScriptInputHelper(null, registry));
|
|
2528
|
+
return { inputs: registry, error: null };
|
|
2449
2529
|
} catch (error) {
|
|
2450
|
-
return error instanceof Error ? error.message : String(error);
|
|
2530
|
+
return { inputs: registry, error: error instanceof Error ? error.message : String(error) };
|
|
2451
2531
|
}
|
|
2452
2532
|
};
|
|
2533
|
+
var validateScriptSource = (source) => extractScriptInputs(source).error;
|
|
2453
2534
|
var drawScriptError = (ctx, renderContext, name, message) => {
|
|
2454
2535
|
ctx.save();
|
|
2455
2536
|
ctx.font = "11px -apple-system, BlinkMacSystemFont, 'Segoe UI', sans-serif";
|
|
@@ -2466,18 +2547,24 @@ var compileScriptIndicator = (definition) => {
|
|
|
2466
2547
|
for (const input of definition.inputs ?? []) {
|
|
2467
2548
|
defaultInputs[input.key] = input.default;
|
|
2468
2549
|
}
|
|
2469
|
-
let
|
|
2550
|
+
let factory = null;
|
|
2470
2551
|
let compileError = null;
|
|
2471
2552
|
const guard = createScriptGuard();
|
|
2472
2553
|
try {
|
|
2473
|
-
|
|
2554
|
+
factory = compileScriptFactory(definition.source);
|
|
2555
|
+
const declared = [];
|
|
2556
|
+
guard.reset();
|
|
2557
|
+
factory(guard, createScriptInputHelper(null, declared));
|
|
2558
|
+
for (const input of declared) {
|
|
2559
|
+
defaultInputs[input.key] = input.default;
|
|
2560
|
+
}
|
|
2474
2561
|
} catch (error) {
|
|
2475
2562
|
compileError = error instanceof Error ? error.message : String(error);
|
|
2476
2563
|
}
|
|
2477
2564
|
const sourceKey = hashScriptSource(definition.source);
|
|
2478
2565
|
let trippedError = null;
|
|
2479
2566
|
const runCompute = (data, inputs) => {
|
|
2480
|
-
if (!
|
|
2567
|
+
if (!factory || compileError) {
|
|
2481
2568
|
return { error: compileError ?? "Script failed to compile" };
|
|
2482
2569
|
}
|
|
2483
2570
|
if (trippedError) {
|
|
@@ -2485,6 +2572,7 @@ var compileScriptIndicator = (definition) => {
|
|
|
2485
2572
|
}
|
|
2486
2573
|
try {
|
|
2487
2574
|
guard.reset();
|
|
2575
|
+
const compiled = factory(guard, createScriptInputHelper(inputs, null));
|
|
2488
2576
|
const result = compiled(data, inputs, SCRIPT_HELPERS);
|
|
2489
2577
|
if (!result || !Array.isArray(result.plots)) {
|
|
2490
2578
|
return { error: "compute() must return { plots: [...] }" };
|
|
@@ -2763,6 +2851,8 @@ var DEFAULT_OPTIONS = {
|
|
|
2763
2851
|
pinOutOfRangeLines: false,
|
|
2764
2852
|
doubleClickEnabled: true,
|
|
2765
2853
|
doubleClickAction: "reset",
|
|
2854
|
+
keyboard: { enabled: true, panBars: 1, deleteSelectedDrawing: true },
|
|
2855
|
+
accessibility: { label: "Price chart", description: "", focusable: true },
|
|
2766
2856
|
crosshair: DEFAULT_CROSSHAIR_OPTIONS,
|
|
2767
2857
|
grid: DEFAULT_GRID_OPTIONS,
|
|
2768
2858
|
watermark: DEFAULT_WATERMARK_OPTIONS,
|
|
@@ -2805,6 +2895,14 @@ var mergeChartOptions = (baseOptions, options = {}) => ({
|
|
|
2805
2895
|
...options.axisColor ? { lineColor: options.axisColor, textColor: options.axisColor } : {},
|
|
2806
2896
|
...options.yAxis ?? {}
|
|
2807
2897
|
},
|
|
2898
|
+
keyboard: {
|
|
2899
|
+
...baseOptions.keyboard,
|
|
2900
|
+
...options.keyboard ?? {}
|
|
2901
|
+
},
|
|
2902
|
+
accessibility: {
|
|
2903
|
+
...baseOptions.accessibility,
|
|
2904
|
+
...options.accessibility ?? {}
|
|
2905
|
+
},
|
|
2808
2906
|
crosshair: {
|
|
2809
2907
|
...baseOptions.crosshair,
|
|
2810
2908
|
...options.crosshair ?? {}
|
|
@@ -2978,7 +3076,29 @@ function createChart(element, options = {}) {
|
|
|
2978
3076
|
let drawingSelectHandler = null;
|
|
2979
3077
|
let drawingDoubleClickHandler = null;
|
|
2980
3078
|
let drawingEditTextHandler = null;
|
|
3079
|
+
let selectionChangeHandler = null;
|
|
3080
|
+
let contextMenuHandler = null;
|
|
3081
|
+
let keyboardShortcutHandler = null;
|
|
3082
|
+
let lastSelectionSignature = null;
|
|
2981
3083
|
let magnetMode = "none";
|
|
3084
|
+
let priceScaleMode = "linear";
|
|
3085
|
+
let priceScaleInverted = false;
|
|
3086
|
+
const PRICE_SCALE_LOG_FLOOR = 1e-9;
|
|
3087
|
+
const priceScaleTransform = (price) => priceScaleMode === "log" ? Math.log10(Math.max(PRICE_SCALE_LOG_FLOOR, price)) : price;
|
|
3088
|
+
const priceScaleUntransform = (value) => priceScaleMode === "log" ? Math.pow(10, value) : value;
|
|
3089
|
+
const percentBaselineForXStart = (xStartValue) => {
|
|
3090
|
+
if (priceScaleMode !== "percent") return null;
|
|
3091
|
+
const bar = data[clamp(Math.round(xStartValue), 0, Math.max(0, data.length - 1))];
|
|
3092
|
+
return bar && bar.c > 0 ? bar.c : null;
|
|
3093
|
+
};
|
|
3094
|
+
const formatPriceForScale = (price, percentBaseline) => {
|
|
3095
|
+
if (percentBaseline !== null) {
|
|
3096
|
+
const pct = (price / percentBaseline - 1) * 100;
|
|
3097
|
+
const rounded = Math.abs(pct) < 5e-3 ? 0 : pct;
|
|
3098
|
+
return `${rounded > 0 ? "+" : ""}${rounded.toFixed(2)}%`;
|
|
3099
|
+
}
|
|
3100
|
+
return formatPrice(price);
|
|
3101
|
+
};
|
|
2982
3102
|
let magnetModifierActive = false;
|
|
2983
3103
|
let shiftKeyActive = false;
|
|
2984
3104
|
const MAGNET_WEAK_THRESHOLD_PX = 16;
|
|
@@ -3122,6 +3242,39 @@ function createChart(element, options = {}) {
|
|
|
3122
3242
|
canvas.style.setProperty("-webkit-user-select", "none");
|
|
3123
3243
|
canvas.style.setProperty("-webkit-touch-callout", "none");
|
|
3124
3244
|
canvas.setAttribute("draggable", "false");
|
|
3245
|
+
const applyAccessibilityAttributes = () => {
|
|
3246
|
+
const a11y = mergedOptions.accessibility ?? {};
|
|
3247
|
+
const focusable = a11y.focusable !== false;
|
|
3248
|
+
if (focusable) {
|
|
3249
|
+
canvas.tabIndex = 0;
|
|
3250
|
+
} else {
|
|
3251
|
+
canvas.removeAttribute("tabindex");
|
|
3252
|
+
}
|
|
3253
|
+
canvas.setAttribute("role", "application");
|
|
3254
|
+
canvas.setAttribute("aria-label", a11y.label ?? "Price chart");
|
|
3255
|
+
if (a11y.description) {
|
|
3256
|
+
canvas.setAttribute("aria-description", a11y.description);
|
|
3257
|
+
} else {
|
|
3258
|
+
canvas.removeAttribute("aria-description");
|
|
3259
|
+
}
|
|
3260
|
+
if (focusable && (mergedOptions.keyboard?.enabled ?? true)) {
|
|
3261
|
+
canvas.setAttribute(
|
|
3262
|
+
"aria-keyshortcuts",
|
|
3263
|
+
"ArrowLeft ArrowRight ArrowUp ArrowDown Plus Minus Home End Delete Escape"
|
|
3264
|
+
);
|
|
3265
|
+
} else {
|
|
3266
|
+
canvas.removeAttribute("aria-keyshortcuts");
|
|
3267
|
+
}
|
|
3268
|
+
};
|
|
3269
|
+
applyAccessibilityAttributes();
|
|
3270
|
+
let focusFromPointer = false;
|
|
3271
|
+
canvas.addEventListener("focus", () => {
|
|
3272
|
+
canvas.style.outline = focusFromPointer ? "none" : "";
|
|
3273
|
+
focusFromPointer = false;
|
|
3274
|
+
});
|
|
3275
|
+
canvas.addEventListener("blur", () => {
|
|
3276
|
+
canvas.style.outline = "none";
|
|
3277
|
+
});
|
|
3125
3278
|
element.innerHTML = "";
|
|
3126
3279
|
element.appendChild(canvas);
|
|
3127
3280
|
const baseCanvas = document.createElement("canvas");
|
|
@@ -4312,9 +4465,14 @@ function createChart(element, options = {}) {
|
|
|
4312
4465
|
yMax
|
|
4313
4466
|
};
|
|
4314
4467
|
plotBottomForHit = fullChartBottom;
|
|
4468
|
+
const scaledYMin = priceScaleTransform(yMin);
|
|
4469
|
+
const scaledYRange = priceScaleTransform(yMax) - scaledYMin || 1;
|
|
4315
4470
|
const yFromPrice = (price) => {
|
|
4316
|
-
|
|
4471
|
+
const ratio = (priceScaleTransform(price) - scaledYMin) / scaledYRange;
|
|
4472
|
+
return priceScaleInverted ? chartTop + ratio * chartHeight : chartBottom - ratio * chartHeight;
|
|
4317
4473
|
};
|
|
4474
|
+
const percentBaseline = percentBaselineForXStart(xStart);
|
|
4475
|
+
const formatAxisPrice = (price) => formatPriceForScale(price, percentBaseline);
|
|
4318
4476
|
const xFromDrawingPoint = (point) => chartLeft + (point.index + 0.5 - xStart) / xSpan * chartWidth;
|
|
4319
4477
|
const FIB_RATIOS = [0, 0.236, 0.382, 0.5, 0.618, 0.786, 1, 1.618];
|
|
4320
4478
|
const FIB_EXT_RATIOS = [0, 0.382, 0.5, 0.618, 1, 1.272, 1.618, 2.618];
|
|
@@ -5221,19 +5379,40 @@ function createChart(element, options = {}) {
|
|
|
5221
5379
|
const yTicks = Math.max(1, Math.floor(yTickCountInput));
|
|
5222
5380
|
const gridColor = grid.color ?? mergedOptions.gridColor;
|
|
5223
5381
|
const priceTicks = [];
|
|
5224
|
-
{
|
|
5225
|
-
|
|
5226
|
-
|
|
5227
|
-
|
|
5228
|
-
|
|
5229
|
-
|
|
5230
|
-
|
|
5231
|
-
|
|
5232
|
-
|
|
5233
|
-
|
|
5382
|
+
const pickNiceStep = (targetStep) => {
|
|
5383
|
+
if (!(targetStep > 0) || !Number.isFinite(targetStep)) return 1;
|
|
5384
|
+
const base = Math.pow(10, Math.floor(Math.log10(targetStep)));
|
|
5385
|
+
const frac = targetStep / base;
|
|
5386
|
+
return (frac <= 1 ? 1 : frac <= 2 ? 2 : frac <= 2.5 ? 2.5 : frac <= 5 ? 5 : 10) * base;
|
|
5387
|
+
};
|
|
5388
|
+
if (percentBaseline !== null) {
|
|
5389
|
+
const pctMin = (yMin / percentBaseline - 1) * 100;
|
|
5390
|
+
const pctMax = (yMax / percentBaseline - 1) * 100;
|
|
5391
|
+
const step = pickNiceStep((pctMax - pctMin) / Math.max(1, yTicks));
|
|
5392
|
+
const firstTick = Math.ceil(pctMin / step) * step;
|
|
5393
|
+
for (let i = 0; ; i += 1) {
|
|
5394
|
+
const pct = firstTick + i * step;
|
|
5395
|
+
if (pct > pctMax + step * 1e-6) break;
|
|
5396
|
+
priceTicks.push(percentBaseline * (1 + pct / 100));
|
|
5397
|
+
if (priceTicks.length > 200) break;
|
|
5398
|
+
}
|
|
5399
|
+
} else if (priceScaleMode === "log" && yMin > 0 && yMax / yMin > 4) {
|
|
5400
|
+
const mantissas = Math.log10(yMax / yMin) > 8 ? [1] : [1, 2, 5];
|
|
5401
|
+
const firstDecade = Math.floor(Math.log10(Math.max(PRICE_SCALE_LOG_FLOOR, yMin)));
|
|
5402
|
+
const lastDecade = Math.ceil(Math.log10(yMax));
|
|
5403
|
+
for (let decade = firstDecade; decade <= lastDecade; decade += 1) {
|
|
5404
|
+
for (const mantissa of mantissas) {
|
|
5405
|
+
const price = mantissa * Math.pow(10, decade);
|
|
5406
|
+
if (price >= yMin && price <= yMax) priceTicks.push(price);
|
|
5407
|
+
if (priceTicks.length > 200) break;
|
|
5234
5408
|
}
|
|
5235
|
-
}
|
|
5236
|
-
|
|
5409
|
+
}
|
|
5410
|
+
} else {
|
|
5411
|
+
const targetStep = yRange / Math.max(1, yTicks);
|
|
5412
|
+
let step = pickNiceStep(targetStep);
|
|
5413
|
+
const instrumentTick = getConfiguredTickSize();
|
|
5414
|
+
if (targetStep > 0 && Number.isFinite(targetStep) && instrumentTick > 0 && step < instrumentTick * 1e6) {
|
|
5415
|
+
step = Math.max(instrumentTick, Math.round(step / instrumentTick) * instrumentTick);
|
|
5237
5416
|
}
|
|
5238
5417
|
const firstTick = Math.ceil(yMin / step) * step;
|
|
5239
5418
|
for (let i = 0; ; i += 1) {
|
|
@@ -5996,7 +6175,7 @@ function createChart(element, options = {}) {
|
|
|
5996
6175
|
ctx.font = `${yAxisFontSize}px ${mergedOptions.fontFamily}`;
|
|
5997
6176
|
for (const price of priceTicks) {
|
|
5998
6177
|
const y = clamp(yFromPrice(price), chartTop + priceScaleTickLabelInset, chartBottom - priceScaleTickLabelInset);
|
|
5999
|
-
drawText(
|
|
6178
|
+
drawText(formatAxisPrice(price), chartRight + 6, y, "left", "middle", yAxis.textColor);
|
|
6000
6179
|
}
|
|
6001
6180
|
ctx.font = prevFont;
|
|
6002
6181
|
}
|
|
@@ -6071,7 +6250,7 @@ function createChart(element, options = {}) {
|
|
|
6071
6250
|
...ticker.showCountdownInLabel ? [getCountdownText()] : []
|
|
6072
6251
|
].map((value) => value === null || value === void 0 ? "" : String(value).trim()).filter((value) => value.length > 0);
|
|
6073
6252
|
addPriceAxisLabel({
|
|
6074
|
-
text:
|
|
6253
|
+
text: formatAxisPrice(tickerPrice),
|
|
6075
6254
|
...tickerSubtexts.length > 0 ? { subtexts: tickerSubtexts } : {},
|
|
6076
6255
|
subtextColor: ticker.labelSubtextColor ?? ticker.labelTextColor ?? "#0b1220",
|
|
6077
6256
|
...ticker.labelSubtextFontSize === void 0 ? {} : { subtextFontSize: ticker.labelSubtextFontSize },
|
|
@@ -6098,7 +6277,7 @@ function createChart(element, options = {}) {
|
|
|
6098
6277
|
const previousClose = previousCloseCandidate;
|
|
6099
6278
|
drawReferenceLine(previousClose, labels.previousCloseColor, "dashed");
|
|
6100
6279
|
addPriceAxisLabel({
|
|
6101
|
-
text: `PDC ${
|
|
6280
|
+
text: `PDC ${formatAxisPrice(previousClose)}`,
|
|
6102
6281
|
price: previousClose,
|
|
6103
6282
|
backgroundColor: labels.backgroundColor,
|
|
6104
6283
|
textColor: labels.mutedTextColor,
|
|
@@ -6117,7 +6296,7 @@ function createChart(element, options = {}) {
|
|
|
6117
6296
|
if (point.l < visibleLow) visibleLow = point.l;
|
|
6118
6297
|
}
|
|
6119
6298
|
addPriceAxisLabel({
|
|
6120
|
-
text: `H ${
|
|
6299
|
+
text: `H ${formatAxisPrice(visibleHigh)}`,
|
|
6121
6300
|
price: visibleHigh,
|
|
6122
6301
|
backgroundColor: labels.backgroundColor,
|
|
6123
6302
|
textColor: labels.textColor,
|
|
@@ -6125,7 +6304,7 @@ function createChart(element, options = {}) {
|
|
|
6125
6304
|
priority: 40
|
|
6126
6305
|
});
|
|
6127
6306
|
addPriceAxisLabel({
|
|
6128
|
-
text: `L ${
|
|
6307
|
+
text: `L ${formatAxisPrice(visibleLow)}`,
|
|
6129
6308
|
price: visibleLow,
|
|
6130
6309
|
backgroundColor: labels.backgroundColor,
|
|
6131
6310
|
textColor: labels.textColor,
|
|
@@ -6137,7 +6316,7 @@ function createChart(element, options = {}) {
|
|
|
6137
6316
|
if (Number.isFinite(labels.bidPrice)) {
|
|
6138
6317
|
drawReferenceLine(labels.bidPrice, labels.bidColor, "dotted");
|
|
6139
6318
|
addPriceAxisLabel({
|
|
6140
|
-
text: `B ${
|
|
6319
|
+
text: `B ${formatAxisPrice(labels.bidPrice)}`,
|
|
6141
6320
|
price: labels.bidPrice,
|
|
6142
6321
|
backgroundColor: labels.bidColor,
|
|
6143
6322
|
textColor: "#0b1220",
|
|
@@ -6148,7 +6327,7 @@ function createChart(element, options = {}) {
|
|
|
6148
6327
|
if (Number.isFinite(labels.askPrice)) {
|
|
6149
6328
|
drawReferenceLine(labels.askPrice, labels.askColor, "dotted");
|
|
6150
6329
|
addPriceAxisLabel({
|
|
6151
|
-
text: `A ${
|
|
6330
|
+
text: `A ${formatAxisPrice(labels.askPrice)}`,
|
|
6152
6331
|
price: labels.askPrice,
|
|
6153
6332
|
backgroundColor: labels.askColor,
|
|
6154
6333
|
textColor: "#0b1220",
|
|
@@ -6323,6 +6502,7 @@ function createChart(element, options = {}) {
|
|
|
6323
6502
|
xSpan
|
|
6324
6503
|
};
|
|
6325
6504
|
paintCrosshairOverlay();
|
|
6505
|
+
emitSelectionChangeIfMoved();
|
|
6326
6506
|
};
|
|
6327
6507
|
const paintCrosshairOverlay = () => {
|
|
6328
6508
|
crosshairPriceActionRegion = null;
|
|
@@ -6409,8 +6589,11 @@ function createChart(element, options = {}) {
|
|
|
6409
6589
|
ctx.restore();
|
|
6410
6590
|
};
|
|
6411
6591
|
if (crosshair.showPriceLabel) {
|
|
6412
|
-
const
|
|
6413
|
-
const
|
|
6592
|
+
const scaledYMin = priceScaleTransform(yMin);
|
|
6593
|
+
const scaledYRange = priceScaleTransform(yMin + yRange) - scaledYMin || 1;
|
|
6594
|
+
const hoverRatio = priceScaleInverted ? (cy - chartTop) / chartHeight : (chartBottom - cy) / chartHeight;
|
|
6595
|
+
const hoverPrice = priceScaleUntransform(scaledYMin + hoverRatio * scaledYRange);
|
|
6596
|
+
const priceText = formatPriceForScale(hoverPrice, percentBaselineForXStart(xStart));
|
|
6414
6597
|
const priceWidth = getRightAxisLabelWidth(chartRight, getPriceLabelWidth(priceText, labelPaddingX));
|
|
6415
6598
|
const priceX = getRightAxisLabelX(chartRight);
|
|
6416
6599
|
const priceY = clamp(cy - labelHeight / 2, chartTop, chartBottom - labelHeight);
|
|
@@ -6578,8 +6761,8 @@ function createChart(element, options = {}) {
|
|
|
6578
6761
|
const maxRange = bounds.hardSpan;
|
|
6579
6762
|
const nextRange = clamp(currentRange * factor, minRange, maxRange);
|
|
6580
6763
|
const anchorRatio = clamp((anchorY - drawState.chartTop) / drawState.chartHeight, 0, 1);
|
|
6581
|
-
const anchorPrice = currentMax - anchorRatio * currentRange;
|
|
6582
|
-
const nextMax = anchorPrice + anchorRatio * nextRange;
|
|
6764
|
+
const anchorPrice = priceScaleInverted ? currentMin + anchorRatio * currentRange : currentMax - anchorRatio * currentRange;
|
|
6765
|
+
const nextMax = priceScaleInverted ? anchorPrice + (1 - anchorRatio) * nextRange : anchorPrice + anchorRatio * nextRange;
|
|
6583
6766
|
const nextMin = nextMax - nextRange;
|
|
6584
6767
|
const clamped = clampYRange(nextMin, nextMax);
|
|
6585
6768
|
yMinOverride = clamped.min;
|
|
@@ -6603,7 +6786,7 @@ function createChart(element, options = {}) {
|
|
|
6603
6786
|
const currentMin = yMinOverride ?? drawState.yMin;
|
|
6604
6787
|
const currentMax = yMaxOverride ?? drawState.yMax;
|
|
6605
6788
|
const range = currentMax - currentMin || 1;
|
|
6606
|
-
const priceShift = deltaY / drawState.chartHeight * range;
|
|
6789
|
+
const priceShift = deltaY / drawState.chartHeight * range * (priceScaleInverted ? -1 : 1);
|
|
6607
6790
|
const clamped = clampYRange(currentMin + priceShift, currentMax + priceShift);
|
|
6608
6791
|
yMinOverride = clamped.min;
|
|
6609
6792
|
yMaxOverride = clamped.max;
|
|
@@ -6836,8 +7019,14 @@ function createChart(element, options = {}) {
|
|
|
6836
7019
|
if (!drawState) {
|
|
6837
7020
|
return 0;
|
|
6838
7021
|
}
|
|
6839
|
-
const ratio = clamp(
|
|
6840
|
-
|
|
7022
|
+
const ratio = clamp(
|
|
7023
|
+
priceScaleInverted ? (y - drawState.chartTop) / drawState.chartHeight : (drawState.chartBottom - y) / drawState.chartHeight,
|
|
7024
|
+
0,
|
|
7025
|
+
1
|
|
7026
|
+
);
|
|
7027
|
+
const scaledMin = priceScaleTransform(drawState.yMin);
|
|
7028
|
+
const scaledMax = priceScaleTransform(drawState.yMax);
|
|
7029
|
+
return priceScaleUntransform(scaledMin + ratio * (scaledMax - scaledMin));
|
|
6841
7030
|
};
|
|
6842
7031
|
const applyMagnet = (y, index, price) => {
|
|
6843
7032
|
const effective = magnetModifierActive ? "strong" : magnetMode;
|
|
@@ -6909,8 +7098,102 @@ function createChart(element, options = {}) {
|
|
|
6909
7098
|
};
|
|
6910
7099
|
const canvasYFromDrawingPrice = (price) => {
|
|
6911
7100
|
if (!drawState) return 0;
|
|
6912
|
-
const
|
|
6913
|
-
|
|
7101
|
+
const scaledMin = priceScaleTransform(drawState.yMin);
|
|
7102
|
+
const scaledRange = priceScaleTransform(drawState.yMax) - scaledMin || 1;
|
|
7103
|
+
const ratio = (priceScaleTransform(price) - scaledMin) / scaledRange;
|
|
7104
|
+
return priceScaleInverted ? drawState.chartTop + ratio * drawState.chartHeight : drawState.chartBottom - ratio * drawState.chartHeight;
|
|
7105
|
+
};
|
|
7106
|
+
const getDrawingBounds = (drawing) => {
|
|
7107
|
+
if (!drawState || drawing.points.length === 0) {
|
|
7108
|
+
return null;
|
|
7109
|
+
}
|
|
7110
|
+
const { chartLeft, chartRight, chartTop, chartBottom } = drawState;
|
|
7111
|
+
let left;
|
|
7112
|
+
let right;
|
|
7113
|
+
let top;
|
|
7114
|
+
let bottom;
|
|
7115
|
+
if (drawing.type === "horizontal-line") {
|
|
7116
|
+
const y = canvasYFromDrawingPrice(drawing.points[0].price);
|
|
7117
|
+
left = chartLeft;
|
|
7118
|
+
right = chartRight;
|
|
7119
|
+
top = y;
|
|
7120
|
+
bottom = y;
|
|
7121
|
+
} else if (drawing.type === "vertical-line") {
|
|
7122
|
+
const x = canvasXFromDrawingPoint(drawing.points[0]);
|
|
7123
|
+
left = x;
|
|
7124
|
+
right = x;
|
|
7125
|
+
top = chartTop;
|
|
7126
|
+
bottom = chartBottom;
|
|
7127
|
+
} else {
|
|
7128
|
+
const xs = drawing.points.map((point) => canvasXFromDrawingPoint(point));
|
|
7129
|
+
const ys = drawing.points.map((point) => canvasYFromDrawingPrice(point.price));
|
|
7130
|
+
left = Math.min(...xs);
|
|
7131
|
+
right = Math.max(...xs);
|
|
7132
|
+
top = Math.min(...ys);
|
|
7133
|
+
bottom = Math.max(...ys);
|
|
7134
|
+
if (drawing.type === "ray") {
|
|
7135
|
+
right = chartRight;
|
|
7136
|
+
}
|
|
7137
|
+
}
|
|
7138
|
+
const clampX = (value) => clamp(value, chartLeft, chartRight);
|
|
7139
|
+
const clampY = (value) => clamp(value, chartTop, chartBottom);
|
|
7140
|
+
left = clampX(left);
|
|
7141
|
+
right = clampX(right);
|
|
7142
|
+
top = clampY(top);
|
|
7143
|
+
bottom = clampY(bottom);
|
|
7144
|
+
return {
|
|
7145
|
+
left,
|
|
7146
|
+
top,
|
|
7147
|
+
right,
|
|
7148
|
+
bottom,
|
|
7149
|
+
centerX: (left + right) / 2,
|
|
7150
|
+
centerY: (top + bottom) / 2
|
|
7151
|
+
};
|
|
7152
|
+
};
|
|
7153
|
+
const getSelectedDrawingWithBounds = () => {
|
|
7154
|
+
if (!selectedDrawingId) {
|
|
7155
|
+
return null;
|
|
7156
|
+
}
|
|
7157
|
+
const drawing = drawings.find((entry) => entry.id === selectedDrawingId);
|
|
7158
|
+
if (!drawing || !drawing.visible) {
|
|
7159
|
+
return null;
|
|
7160
|
+
}
|
|
7161
|
+
const bounds = getDrawingBounds(drawing);
|
|
7162
|
+
return bounds ? { drawing: serializeDrawing(drawing), bounds } : null;
|
|
7163
|
+
};
|
|
7164
|
+
const emitSelectionChangeIfMoved = () => {
|
|
7165
|
+
if (!selectionChangeHandler) {
|
|
7166
|
+
lastSelectionSignature = null;
|
|
7167
|
+
return;
|
|
7168
|
+
}
|
|
7169
|
+
const selection = getSelectedDrawingWithBounds();
|
|
7170
|
+
if (!selection || !drawState) {
|
|
7171
|
+
if (lastSelectionSignature !== null) {
|
|
7172
|
+
lastSelectionSignature = null;
|
|
7173
|
+
selectionChangeHandler(null);
|
|
7174
|
+
}
|
|
7175
|
+
return;
|
|
7176
|
+
}
|
|
7177
|
+
const dragging = drawingDragState !== null;
|
|
7178
|
+
const { bounds } = selection;
|
|
7179
|
+
const signature = `${selection.drawing.id}:${Math.round(bounds.left)}:${Math.round(bounds.top)}:${Math.round(
|
|
7180
|
+
bounds.right
|
|
7181
|
+
)}:${Math.round(bounds.bottom)}:${dragging ? 1 : 0}`;
|
|
7182
|
+
if (signature === lastSelectionSignature) {
|
|
7183
|
+
return;
|
|
7184
|
+
}
|
|
7185
|
+
lastSelectionSignature = signature;
|
|
7186
|
+
selectionChangeHandler({
|
|
7187
|
+
drawing: selection.drawing,
|
|
7188
|
+
bounds,
|
|
7189
|
+
plot: {
|
|
7190
|
+
left: drawState.chartLeft,
|
|
7191
|
+
top: drawState.chartTop,
|
|
7192
|
+
right: drawState.chartRight,
|
|
7193
|
+
bottom: drawState.chartBottom
|
|
7194
|
+
},
|
|
7195
|
+
dragging
|
|
7196
|
+
});
|
|
6914
7197
|
};
|
|
6915
7198
|
const distanceToSegment = (x, y, x1, y1, x2, y2) => {
|
|
6916
7199
|
const dx = x2 - x1;
|
|
@@ -7878,6 +8161,10 @@ function createChart(element, options = {}) {
|
|
|
7878
8161
|
if (event.pointerType === "touch" || event.pointerType === "pen") {
|
|
7879
8162
|
event.preventDefault();
|
|
7880
8163
|
}
|
|
8164
|
+
if (mergedOptions.accessibility?.focusable !== false && document.activeElement !== canvas) {
|
|
8165
|
+
focusFromPointer = true;
|
|
8166
|
+
canvas.focus({ preventScroll: true });
|
|
8167
|
+
}
|
|
7881
8168
|
cancelKineticPan();
|
|
7882
8169
|
panVelocitySamples.length = 0;
|
|
7883
8170
|
magnetModifierActive = event.metaKey || event.ctrlKey;
|
|
@@ -7995,12 +8282,14 @@ function createChart(element, options = {}) {
|
|
|
7995
8282
|
const drawingHit = getDrawingHit(point.x, point.y);
|
|
7996
8283
|
if (drawingHit) {
|
|
7997
8284
|
selectedDrawingId = drawingHit.drawing.id;
|
|
8285
|
+
const selectBounds = getDrawingBounds(drawingHit.drawing);
|
|
7998
8286
|
drawingSelectHandler?.({
|
|
7999
8287
|
drawing: serializeDrawing(drawingHit.drawing),
|
|
8000
8288
|
target: drawingHit.target,
|
|
8001
8289
|
...drawingHit.pointIndex === void 0 ? {} : { pointIndex: drawingHit.pointIndex },
|
|
8002
8290
|
x: point.x,
|
|
8003
|
-
y: point.y
|
|
8291
|
+
y: point.y,
|
|
8292
|
+
...selectBounds ? { bounds: selectBounds } : {}
|
|
8004
8293
|
});
|
|
8005
8294
|
if (!drawingHit.drawing.locked) {
|
|
8006
8295
|
const startCanvasPoint = drawingPointFromCanvas(point.x, point.y, false);
|
|
@@ -8561,7 +8850,152 @@ function createChart(element, options = {}) {
|
|
|
8561
8850
|
};
|
|
8562
8851
|
const onContextMenu = (event) => {
|
|
8563
8852
|
event.preventDefault();
|
|
8853
|
+
if (!contextMenuHandler || !drawState) {
|
|
8854
|
+
return;
|
|
8855
|
+
}
|
|
8856
|
+
const point = getCanvasPoint(event);
|
|
8857
|
+
const hitRegion = getHitRegion(point.x, point.y);
|
|
8858
|
+
if (hitRegion === "outside") {
|
|
8859
|
+
return;
|
|
8860
|
+
}
|
|
8861
|
+
const pane = hitRegion === "plot" ? getPaneAt(point.x, point.y) : null;
|
|
8862
|
+
const drawingHit = hitRegion === "plot" && !pane ? getDrawingHit(point.x, point.y) : null;
|
|
8863
|
+
if (drawingHit) {
|
|
8864
|
+
if (selectedDrawingId !== drawingHit.drawing.id) {
|
|
8865
|
+
selectedDrawingId = drawingHit.drawing.id;
|
|
8866
|
+
scheduleDraw();
|
|
8867
|
+
}
|
|
8868
|
+
}
|
|
8869
|
+
const region = drawingHit ? "drawing" : pane ? "indicator-pane" : hitRegion;
|
|
8870
|
+
const index = indexFromCanvasX(point.x);
|
|
8871
|
+
const barTime = index === null ? null : getTimeForIndex(index);
|
|
8872
|
+
const bar = index === null ? void 0 : data[index];
|
|
8873
|
+
const inMainPane = !pane && (hitRegion === "plot" || hitRegion === "y-axis");
|
|
8874
|
+
contextMenuHandler({
|
|
8875
|
+
region,
|
|
8876
|
+
x: point.x,
|
|
8877
|
+
y: point.y,
|
|
8878
|
+
clientX: event.clientX,
|
|
8879
|
+
clientY: event.clientY,
|
|
8880
|
+
...inMainPane ? { price: roundToPricePrecision(priceFromCanvasY(point.y)) } : {},
|
|
8881
|
+
...index === null ? {} : { index },
|
|
8882
|
+
...barTime ? { time: barTime.toISOString() } : {},
|
|
8883
|
+
...bar ? {
|
|
8884
|
+
point: {
|
|
8885
|
+
t: bar.time.toISOString(),
|
|
8886
|
+
o: bar.o,
|
|
8887
|
+
h: bar.h,
|
|
8888
|
+
l: bar.l,
|
|
8889
|
+
c: bar.c,
|
|
8890
|
+
...bar.v === void 0 ? {} : { v: bar.v }
|
|
8891
|
+
}
|
|
8892
|
+
} : {},
|
|
8893
|
+
...drawingHit ? {
|
|
8894
|
+
drawing: serializeDrawing(drawingHit.drawing),
|
|
8895
|
+
drawingTarget: drawingHit.target,
|
|
8896
|
+
...drawingHit.pointIndex === void 0 ? {} : { pointIndex: drawingHit.pointIndex }
|
|
8897
|
+
} : {},
|
|
8898
|
+
...pane ? { indicator: { id: pane.id, type: pane.type } } : {}
|
|
8899
|
+
});
|
|
8564
8900
|
};
|
|
8901
|
+
const onCanvasKeyDown = (event) => {
|
|
8902
|
+
const keyboard = mergedOptions.keyboard ?? {};
|
|
8903
|
+
if (keyboard.enabled === false) {
|
|
8904
|
+
return;
|
|
8905
|
+
}
|
|
8906
|
+
if (event.altKey || event.ctrlKey || event.metaKey) {
|
|
8907
|
+
return;
|
|
8908
|
+
}
|
|
8909
|
+
const panStep = Math.max(1, keyboard.panBars ?? 1) * (event.shiftKey ? 10 : 1);
|
|
8910
|
+
const emit = (action, drawing) => {
|
|
8911
|
+
keyboardShortcutHandler?.({
|
|
8912
|
+
action,
|
|
8913
|
+
key: event.key,
|
|
8914
|
+
shiftKey: event.shiftKey,
|
|
8915
|
+
...drawing ? { drawing } : {}
|
|
8916
|
+
});
|
|
8917
|
+
};
|
|
8918
|
+
switch (event.key) {
|
|
8919
|
+
case "Delete":
|
|
8920
|
+
case "Backspace": {
|
|
8921
|
+
if (keyboard.deleteSelectedDrawing === false || !selectedDrawingId) {
|
|
8922
|
+
return;
|
|
8923
|
+
}
|
|
8924
|
+
const target = drawings.find((entry) => entry.id === selectedDrawingId);
|
|
8925
|
+
if (!target) {
|
|
8926
|
+
return;
|
|
8927
|
+
}
|
|
8928
|
+
const removed = serializeDrawing(target);
|
|
8929
|
+
removeDrawing(selectedDrawingId);
|
|
8930
|
+
selectedDrawingId = null;
|
|
8931
|
+
scheduleDraw();
|
|
8932
|
+
emit("delete-drawing", removed);
|
|
8933
|
+
break;
|
|
8934
|
+
}
|
|
8935
|
+
case "Escape": {
|
|
8936
|
+
if (cancelDrawing()) {
|
|
8937
|
+
emit("cancel");
|
|
8938
|
+
break;
|
|
8939
|
+
}
|
|
8940
|
+
if (activeDrawingTool) {
|
|
8941
|
+
setActiveDrawingTool(null);
|
|
8942
|
+
emit("cancel");
|
|
8943
|
+
break;
|
|
8944
|
+
}
|
|
8945
|
+
if (selectedDrawingId) {
|
|
8946
|
+
setSelectedDrawing(null);
|
|
8947
|
+
emit("cancel");
|
|
8948
|
+
break;
|
|
8949
|
+
}
|
|
8950
|
+
return;
|
|
8951
|
+
}
|
|
8952
|
+
case "ArrowLeft":
|
|
8953
|
+
panX(-panStep);
|
|
8954
|
+
emit("pan-left");
|
|
8955
|
+
break;
|
|
8956
|
+
case "ArrowRight":
|
|
8957
|
+
panX(panStep);
|
|
8958
|
+
emit("pan-right");
|
|
8959
|
+
break;
|
|
8960
|
+
case "ArrowUp":
|
|
8961
|
+
case "ArrowDown": {
|
|
8962
|
+
if (!drawState) {
|
|
8963
|
+
return;
|
|
8964
|
+
}
|
|
8965
|
+
const step = (drawState.yMax - drawState.yMin) / 20 * (event.shiftKey ? 5 : 1);
|
|
8966
|
+
panY(event.key === "ArrowUp" ? step : -step);
|
|
8967
|
+
emit(event.key === "ArrowUp" ? "pan-up" : "pan-down");
|
|
8968
|
+
break;
|
|
8969
|
+
}
|
|
8970
|
+
case "+":
|
|
8971
|
+
case "=":
|
|
8972
|
+
zoomInX();
|
|
8973
|
+
emit("zoom-in");
|
|
8974
|
+
break;
|
|
8975
|
+
case "-":
|
|
8976
|
+
case "_":
|
|
8977
|
+
zoomOutX();
|
|
8978
|
+
emit("zoom-out");
|
|
8979
|
+
break;
|
|
8980
|
+
case "Home":
|
|
8981
|
+
fitContent();
|
|
8982
|
+
emit("reset-viewport");
|
|
8983
|
+
break;
|
|
8984
|
+
case "End":
|
|
8985
|
+
setFollowingLatest(true);
|
|
8986
|
+
emit("scroll-to-realtime");
|
|
8987
|
+
break;
|
|
8988
|
+
case "r":
|
|
8989
|
+
case "R":
|
|
8990
|
+
resetViewport();
|
|
8991
|
+
emit("reset-viewport");
|
|
8992
|
+
break;
|
|
8993
|
+
default:
|
|
8994
|
+
return;
|
|
8995
|
+
}
|
|
8996
|
+
event.preventDefault();
|
|
8997
|
+
};
|
|
8998
|
+
canvas.addEventListener("keydown", onCanvasKeyDown);
|
|
8565
8999
|
canvas.addEventListener("pointerdown", onPointerDown);
|
|
8566
9000
|
canvas.addEventListener("pointermove", onPointerMove);
|
|
8567
9001
|
canvas.addEventListener("pointerup", endPointerDrag);
|
|
@@ -8608,6 +9042,7 @@ function createChart(element, options = {}) {
|
|
|
8608
9042
|
resetRightMarginCache();
|
|
8609
9043
|
doubleClickEnabled = mergedOptions.doubleClickEnabled;
|
|
8610
9044
|
doubleClickAction = mergedOptions.doubleClickAction;
|
|
9045
|
+
applyAccessibilityAttributes();
|
|
8611
9046
|
const isTickerSmoothingEnabled = mergedOptions.tickerLine?.smoothing ?? false;
|
|
8612
9047
|
if (!isTickerSmoothingEnabled) {
|
|
8613
9048
|
smoothedTickerPrice = null;
|
|
@@ -8924,6 +9359,16 @@ function createChart(element, options = {}) {
|
|
|
8924
9359
|
magnetMode = mode;
|
|
8925
9360
|
};
|
|
8926
9361
|
const getMagnetMode = () => magnetMode;
|
|
9362
|
+
const setPriceScale = (options2) => {
|
|
9363
|
+
if (options2.mode === "linear" || options2.mode === "log" || options2.mode === "percent") {
|
|
9364
|
+
priceScaleMode = options2.mode;
|
|
9365
|
+
}
|
|
9366
|
+
if (typeof options2.inverted === "boolean") {
|
|
9367
|
+
priceScaleInverted = options2.inverted;
|
|
9368
|
+
}
|
|
9369
|
+
scheduleDraw();
|
|
9370
|
+
};
|
|
9371
|
+
const getPriceScale = () => ({ mode: priceScaleMode, inverted: priceScaleInverted });
|
|
8927
9372
|
const getDrawings = () => drawings.map((drawing) => serializeDrawing(drawing));
|
|
8928
9373
|
const setDrawings = (nextDrawings) => {
|
|
8929
9374
|
drawings = dedupeDrawingIds(nextDrawings.map((drawing) => normalizeDrawingState(drawing)));
|
|
@@ -8981,6 +9426,23 @@ function createChart(element, options = {}) {
|
|
|
8981
9426
|
const onDrawingHover = (handler) => {
|
|
8982
9427
|
drawingHoverHandler = handler;
|
|
8983
9428
|
};
|
|
9429
|
+
const onSelectionChange = (handler) => {
|
|
9430
|
+
selectionChangeHandler = handler;
|
|
9431
|
+
lastSelectionSignature = null;
|
|
9432
|
+
if (handler) {
|
|
9433
|
+
emitSelectionChangeIfMoved();
|
|
9434
|
+
}
|
|
9435
|
+
};
|
|
9436
|
+
const getSelectedDrawing = () => getSelectedDrawingWithBounds();
|
|
9437
|
+
const onContextMenuHandler = (handler) => {
|
|
9438
|
+
contextMenuHandler = handler;
|
|
9439
|
+
};
|
|
9440
|
+
const onKeyboardShortcut = (handler) => {
|
|
9441
|
+
keyboardShortcutHandler = handler;
|
|
9442
|
+
};
|
|
9443
|
+
const focus = () => {
|
|
9444
|
+
canvas.focus({ preventScroll: true });
|
|
9445
|
+
};
|
|
8984
9446
|
const saveState = () => {
|
|
8985
9447
|
const drawingDefaults = {};
|
|
8986
9448
|
for (const [tool, defaults] of drawingToolDefaults) {
|
|
@@ -8993,6 +9455,7 @@ function createChart(element, options = {}) {
|
|
|
8993
9455
|
drawings: getDrawings(),
|
|
8994
9456
|
indicators: getIndicators(),
|
|
8995
9457
|
magnetMode: getMagnetMode(),
|
|
9458
|
+
priceScale: getPriceScale(),
|
|
8996
9459
|
drawingDefaults
|
|
8997
9460
|
};
|
|
8998
9461
|
};
|
|
@@ -9012,6 +9475,9 @@ function createChart(element, options = {}) {
|
|
|
9012
9475
|
if (state.magnetMode === "none" || state.magnetMode === "weak" || state.magnetMode === "strong") {
|
|
9013
9476
|
setMagnetMode(state.magnetMode);
|
|
9014
9477
|
}
|
|
9478
|
+
if (state.priceScale && typeof state.priceScale === "object") {
|
|
9479
|
+
setPriceScale(state.priceScale);
|
|
9480
|
+
}
|
|
9015
9481
|
if (state.drawingDefaults && typeof state.drawingDefaults === "object") {
|
|
9016
9482
|
for (const [tool, defaults] of Object.entries(state.drawingDefaults)) {
|
|
9017
9483
|
if (defaults && typeof defaults === "object") {
|
|
@@ -9041,6 +9507,7 @@ function createChart(element, options = {}) {
|
|
|
9041
9507
|
drawRafId = null;
|
|
9042
9508
|
pendingDrawUpdateAutoScale = false;
|
|
9043
9509
|
}
|
|
9510
|
+
canvas.removeEventListener("keydown", onCanvasKeyDown);
|
|
9044
9511
|
canvas.removeEventListener("pointerdown", onPointerDown);
|
|
9045
9512
|
canvas.removeEventListener("pointermove", onPointerMove);
|
|
9046
9513
|
canvas.removeEventListener("pointerup", endPointerDrag);
|
|
@@ -9104,8 +9571,15 @@ function createChart(element, options = {}) {
|
|
|
9104
9571
|
onDrawingSelect,
|
|
9105
9572
|
onDrawingDoubleClick,
|
|
9106
9573
|
onDrawingEditText,
|
|
9574
|
+
onSelectionChange,
|
|
9575
|
+
getSelectedDrawing,
|
|
9576
|
+
onContextMenu: onContextMenuHandler,
|
|
9577
|
+
onKeyboardShortcut,
|
|
9578
|
+
focus,
|
|
9107
9579
|
setMagnetMode,
|
|
9108
9580
|
getMagnetMode,
|
|
9581
|
+
setPriceScale,
|
|
9582
|
+
getPriceScale,
|
|
9109
9583
|
cancelDrawing,
|
|
9110
9584
|
setTradeMarkers,
|
|
9111
9585
|
setSelectedDrawing,
|
|
@@ -9132,7 +9606,9 @@ function createChart(element, options = {}) {
|
|
|
9132
9606
|
0 && (module.exports = {
|
|
9133
9607
|
FIB_DEFAULT_PALETTE,
|
|
9134
9608
|
POSITION_DEFAULT_COLORS,
|
|
9609
|
+
SCRIPT_SOURCE_INPUT_VALUES,
|
|
9135
9610
|
compileScriptIndicator,
|
|
9136
9611
|
createChart,
|
|
9612
|
+
extractScriptInputs,
|
|
9137
9613
|
validateScriptSource
|
|
9138
9614
|
});
|