hyperprop-charting-library 0.1.136 → 0.1.137
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 +203 -42
- package/dist/hyperprop-charting-library.d.ts +56 -1
- package/dist/hyperprop-charting-library.js +201 -42
- package/dist/index.cjs +203 -42
- package/dist/index.d.cts +56 -1
- package/dist/index.d.ts +56 -1
- package/dist/index.js +201 -42
- package/docs/API.md +39 -1
- package/package.json +1 -1
|
@@ -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: [...] }" };
|
|
@@ -2949,6 +3035,24 @@ function createChart(element, options = {}) {
|
|
|
2949
3035
|
let drawingDoubleClickHandler = null;
|
|
2950
3036
|
let drawingEditTextHandler = null;
|
|
2951
3037
|
let magnetMode = "none";
|
|
3038
|
+
let priceScaleMode = "linear";
|
|
3039
|
+
let priceScaleInverted = false;
|
|
3040
|
+
const PRICE_SCALE_LOG_FLOOR = 1e-9;
|
|
3041
|
+
const priceScaleTransform = (price) => priceScaleMode === "log" ? Math.log10(Math.max(PRICE_SCALE_LOG_FLOOR, price)) : price;
|
|
3042
|
+
const priceScaleUntransform = (value) => priceScaleMode === "log" ? Math.pow(10, value) : value;
|
|
3043
|
+
const percentBaselineForXStart = (xStartValue) => {
|
|
3044
|
+
if (priceScaleMode !== "percent") return null;
|
|
3045
|
+
const bar = data[clamp(Math.round(xStartValue), 0, Math.max(0, data.length - 1))];
|
|
3046
|
+
return bar && bar.c > 0 ? bar.c : null;
|
|
3047
|
+
};
|
|
3048
|
+
const formatPriceForScale = (price, percentBaseline) => {
|
|
3049
|
+
if (percentBaseline !== null) {
|
|
3050
|
+
const pct = (price / percentBaseline - 1) * 100;
|
|
3051
|
+
const rounded = Math.abs(pct) < 5e-3 ? 0 : pct;
|
|
3052
|
+
return `${rounded > 0 ? "+" : ""}${rounded.toFixed(2)}%`;
|
|
3053
|
+
}
|
|
3054
|
+
return formatPrice(price);
|
|
3055
|
+
};
|
|
2952
3056
|
let magnetModifierActive = false;
|
|
2953
3057
|
let shiftKeyActive = false;
|
|
2954
3058
|
const MAGNET_WEAK_THRESHOLD_PX = 16;
|
|
@@ -4282,9 +4386,14 @@ function createChart(element, options = {}) {
|
|
|
4282
4386
|
yMax
|
|
4283
4387
|
};
|
|
4284
4388
|
plotBottomForHit = fullChartBottom;
|
|
4389
|
+
const scaledYMin = priceScaleTransform(yMin);
|
|
4390
|
+
const scaledYRange = priceScaleTransform(yMax) - scaledYMin || 1;
|
|
4285
4391
|
const yFromPrice = (price) => {
|
|
4286
|
-
|
|
4392
|
+
const ratio = (priceScaleTransform(price) - scaledYMin) / scaledYRange;
|
|
4393
|
+
return priceScaleInverted ? chartTop + ratio * chartHeight : chartBottom - ratio * chartHeight;
|
|
4287
4394
|
};
|
|
4395
|
+
const percentBaseline = percentBaselineForXStart(xStart);
|
|
4396
|
+
const formatAxisPrice = (price) => formatPriceForScale(price, percentBaseline);
|
|
4288
4397
|
const xFromDrawingPoint = (point) => chartLeft + (point.index + 0.5 - xStart) / xSpan * chartWidth;
|
|
4289
4398
|
const FIB_RATIOS = [0, 0.236, 0.382, 0.5, 0.618, 0.786, 1, 1.618];
|
|
4290
4399
|
const FIB_EXT_RATIOS = [0, 0.382, 0.5, 0.618, 1, 1.272, 1.618, 2.618];
|
|
@@ -5191,19 +5300,40 @@ function createChart(element, options = {}) {
|
|
|
5191
5300
|
const yTicks = Math.max(1, Math.floor(yTickCountInput));
|
|
5192
5301
|
const gridColor = grid.color ?? mergedOptions.gridColor;
|
|
5193
5302
|
const priceTicks = [];
|
|
5194
|
-
{
|
|
5195
|
-
|
|
5196
|
-
|
|
5197
|
-
|
|
5198
|
-
|
|
5199
|
-
|
|
5200
|
-
|
|
5201
|
-
|
|
5202
|
-
|
|
5203
|
-
|
|
5303
|
+
const pickNiceStep = (targetStep) => {
|
|
5304
|
+
if (!(targetStep > 0) || !Number.isFinite(targetStep)) return 1;
|
|
5305
|
+
const base = Math.pow(10, Math.floor(Math.log10(targetStep)));
|
|
5306
|
+
const frac = targetStep / base;
|
|
5307
|
+
return (frac <= 1 ? 1 : frac <= 2 ? 2 : frac <= 2.5 ? 2.5 : frac <= 5 ? 5 : 10) * base;
|
|
5308
|
+
};
|
|
5309
|
+
if (percentBaseline !== null) {
|
|
5310
|
+
const pctMin = (yMin / percentBaseline - 1) * 100;
|
|
5311
|
+
const pctMax = (yMax / percentBaseline - 1) * 100;
|
|
5312
|
+
const step = pickNiceStep((pctMax - pctMin) / Math.max(1, yTicks));
|
|
5313
|
+
const firstTick = Math.ceil(pctMin / step) * step;
|
|
5314
|
+
for (let i = 0; ; i += 1) {
|
|
5315
|
+
const pct = firstTick + i * step;
|
|
5316
|
+
if (pct > pctMax + step * 1e-6) break;
|
|
5317
|
+
priceTicks.push(percentBaseline * (1 + pct / 100));
|
|
5318
|
+
if (priceTicks.length > 200) break;
|
|
5319
|
+
}
|
|
5320
|
+
} else if (priceScaleMode === "log" && yMin > 0 && yMax / yMin > 4) {
|
|
5321
|
+
const mantissas = Math.log10(yMax / yMin) > 8 ? [1] : [1, 2, 5];
|
|
5322
|
+
const firstDecade = Math.floor(Math.log10(Math.max(PRICE_SCALE_LOG_FLOOR, yMin)));
|
|
5323
|
+
const lastDecade = Math.ceil(Math.log10(yMax));
|
|
5324
|
+
for (let decade = firstDecade; decade <= lastDecade; decade += 1) {
|
|
5325
|
+
for (const mantissa of mantissas) {
|
|
5326
|
+
const price = mantissa * Math.pow(10, decade);
|
|
5327
|
+
if (price >= yMin && price <= yMax) priceTicks.push(price);
|
|
5328
|
+
if (priceTicks.length > 200) break;
|
|
5204
5329
|
}
|
|
5205
|
-
}
|
|
5206
|
-
|
|
5330
|
+
}
|
|
5331
|
+
} else {
|
|
5332
|
+
const targetStep = yRange / Math.max(1, yTicks);
|
|
5333
|
+
let step = pickNiceStep(targetStep);
|
|
5334
|
+
const instrumentTick = getConfiguredTickSize();
|
|
5335
|
+
if (targetStep > 0 && Number.isFinite(targetStep) && instrumentTick > 0 && step < instrumentTick * 1e6) {
|
|
5336
|
+
step = Math.max(instrumentTick, Math.round(step / instrumentTick) * instrumentTick);
|
|
5207
5337
|
}
|
|
5208
5338
|
const firstTick = Math.ceil(yMin / step) * step;
|
|
5209
5339
|
for (let i = 0; ; i += 1) {
|
|
@@ -5966,7 +6096,7 @@ function createChart(element, options = {}) {
|
|
|
5966
6096
|
ctx.font = `${yAxisFontSize}px ${mergedOptions.fontFamily}`;
|
|
5967
6097
|
for (const price of priceTicks) {
|
|
5968
6098
|
const y = clamp(yFromPrice(price), chartTop + priceScaleTickLabelInset, chartBottom - priceScaleTickLabelInset);
|
|
5969
|
-
drawText(
|
|
6099
|
+
drawText(formatAxisPrice(price), chartRight + 6, y, "left", "middle", yAxis.textColor);
|
|
5970
6100
|
}
|
|
5971
6101
|
ctx.font = prevFont;
|
|
5972
6102
|
}
|
|
@@ -6041,7 +6171,7 @@ function createChart(element, options = {}) {
|
|
|
6041
6171
|
...ticker.showCountdownInLabel ? [getCountdownText()] : []
|
|
6042
6172
|
].map((value) => value === null || value === void 0 ? "" : String(value).trim()).filter((value) => value.length > 0);
|
|
6043
6173
|
addPriceAxisLabel({
|
|
6044
|
-
text:
|
|
6174
|
+
text: formatAxisPrice(tickerPrice),
|
|
6045
6175
|
...tickerSubtexts.length > 0 ? { subtexts: tickerSubtexts } : {},
|
|
6046
6176
|
subtextColor: ticker.labelSubtextColor ?? ticker.labelTextColor ?? "#0b1220",
|
|
6047
6177
|
...ticker.labelSubtextFontSize === void 0 ? {} : { subtextFontSize: ticker.labelSubtextFontSize },
|
|
@@ -6068,7 +6198,7 @@ function createChart(element, options = {}) {
|
|
|
6068
6198
|
const previousClose = previousCloseCandidate;
|
|
6069
6199
|
drawReferenceLine(previousClose, labels.previousCloseColor, "dashed");
|
|
6070
6200
|
addPriceAxisLabel({
|
|
6071
|
-
text: `PDC ${
|
|
6201
|
+
text: `PDC ${formatAxisPrice(previousClose)}`,
|
|
6072
6202
|
price: previousClose,
|
|
6073
6203
|
backgroundColor: labels.backgroundColor,
|
|
6074
6204
|
textColor: labels.mutedTextColor,
|
|
@@ -6087,7 +6217,7 @@ function createChart(element, options = {}) {
|
|
|
6087
6217
|
if (point.l < visibleLow) visibleLow = point.l;
|
|
6088
6218
|
}
|
|
6089
6219
|
addPriceAxisLabel({
|
|
6090
|
-
text: `H ${
|
|
6220
|
+
text: `H ${formatAxisPrice(visibleHigh)}`,
|
|
6091
6221
|
price: visibleHigh,
|
|
6092
6222
|
backgroundColor: labels.backgroundColor,
|
|
6093
6223
|
textColor: labels.textColor,
|
|
@@ -6095,7 +6225,7 @@ function createChart(element, options = {}) {
|
|
|
6095
6225
|
priority: 40
|
|
6096
6226
|
});
|
|
6097
6227
|
addPriceAxisLabel({
|
|
6098
|
-
text: `L ${
|
|
6228
|
+
text: `L ${formatAxisPrice(visibleLow)}`,
|
|
6099
6229
|
price: visibleLow,
|
|
6100
6230
|
backgroundColor: labels.backgroundColor,
|
|
6101
6231
|
textColor: labels.textColor,
|
|
@@ -6107,7 +6237,7 @@ function createChart(element, options = {}) {
|
|
|
6107
6237
|
if (Number.isFinite(labels.bidPrice)) {
|
|
6108
6238
|
drawReferenceLine(labels.bidPrice, labels.bidColor, "dotted");
|
|
6109
6239
|
addPriceAxisLabel({
|
|
6110
|
-
text: `B ${
|
|
6240
|
+
text: `B ${formatAxisPrice(labels.bidPrice)}`,
|
|
6111
6241
|
price: labels.bidPrice,
|
|
6112
6242
|
backgroundColor: labels.bidColor,
|
|
6113
6243
|
textColor: "#0b1220",
|
|
@@ -6118,7 +6248,7 @@ function createChart(element, options = {}) {
|
|
|
6118
6248
|
if (Number.isFinite(labels.askPrice)) {
|
|
6119
6249
|
drawReferenceLine(labels.askPrice, labels.askColor, "dotted");
|
|
6120
6250
|
addPriceAxisLabel({
|
|
6121
|
-
text: `A ${
|
|
6251
|
+
text: `A ${formatAxisPrice(labels.askPrice)}`,
|
|
6122
6252
|
price: labels.askPrice,
|
|
6123
6253
|
backgroundColor: labels.askColor,
|
|
6124
6254
|
textColor: "#0b1220",
|
|
@@ -6379,8 +6509,11 @@ function createChart(element, options = {}) {
|
|
|
6379
6509
|
ctx.restore();
|
|
6380
6510
|
};
|
|
6381
6511
|
if (crosshair.showPriceLabel) {
|
|
6382
|
-
const
|
|
6383
|
-
const
|
|
6512
|
+
const scaledYMin = priceScaleTransform(yMin);
|
|
6513
|
+
const scaledYRange = priceScaleTransform(yMin + yRange) - scaledYMin || 1;
|
|
6514
|
+
const hoverRatio = priceScaleInverted ? (cy - chartTop) / chartHeight : (chartBottom - cy) / chartHeight;
|
|
6515
|
+
const hoverPrice = priceScaleUntransform(scaledYMin + hoverRatio * scaledYRange);
|
|
6516
|
+
const priceText = formatPriceForScale(hoverPrice, percentBaselineForXStart(xStart));
|
|
6384
6517
|
const priceWidth = getRightAxisLabelWidth(chartRight, getPriceLabelWidth(priceText, labelPaddingX));
|
|
6385
6518
|
const priceX = getRightAxisLabelX(chartRight);
|
|
6386
6519
|
const priceY = clamp(cy - labelHeight / 2, chartTop, chartBottom - labelHeight);
|
|
@@ -6548,8 +6681,8 @@ function createChart(element, options = {}) {
|
|
|
6548
6681
|
const maxRange = bounds.hardSpan;
|
|
6549
6682
|
const nextRange = clamp(currentRange * factor, minRange, maxRange);
|
|
6550
6683
|
const anchorRatio = clamp((anchorY - drawState.chartTop) / drawState.chartHeight, 0, 1);
|
|
6551
|
-
const anchorPrice = currentMax - anchorRatio * currentRange;
|
|
6552
|
-
const nextMax = anchorPrice + anchorRatio * nextRange;
|
|
6684
|
+
const anchorPrice = priceScaleInverted ? currentMin + anchorRatio * currentRange : currentMax - anchorRatio * currentRange;
|
|
6685
|
+
const nextMax = priceScaleInverted ? anchorPrice + (1 - anchorRatio) * nextRange : anchorPrice + anchorRatio * nextRange;
|
|
6553
6686
|
const nextMin = nextMax - nextRange;
|
|
6554
6687
|
const clamped = clampYRange(nextMin, nextMax);
|
|
6555
6688
|
yMinOverride = clamped.min;
|
|
@@ -6573,7 +6706,7 @@ function createChart(element, options = {}) {
|
|
|
6573
6706
|
const currentMin = yMinOverride ?? drawState.yMin;
|
|
6574
6707
|
const currentMax = yMaxOverride ?? drawState.yMax;
|
|
6575
6708
|
const range = currentMax - currentMin || 1;
|
|
6576
|
-
const priceShift = deltaY / drawState.chartHeight * range;
|
|
6709
|
+
const priceShift = deltaY / drawState.chartHeight * range * (priceScaleInverted ? -1 : 1);
|
|
6577
6710
|
const clamped = clampYRange(currentMin + priceShift, currentMax + priceShift);
|
|
6578
6711
|
yMinOverride = clamped.min;
|
|
6579
6712
|
yMaxOverride = clamped.max;
|
|
@@ -6806,8 +6939,14 @@ function createChart(element, options = {}) {
|
|
|
6806
6939
|
if (!drawState) {
|
|
6807
6940
|
return 0;
|
|
6808
6941
|
}
|
|
6809
|
-
const ratio = clamp(
|
|
6810
|
-
|
|
6942
|
+
const ratio = clamp(
|
|
6943
|
+
priceScaleInverted ? (y - drawState.chartTop) / drawState.chartHeight : (drawState.chartBottom - y) / drawState.chartHeight,
|
|
6944
|
+
0,
|
|
6945
|
+
1
|
|
6946
|
+
);
|
|
6947
|
+
const scaledMin = priceScaleTransform(drawState.yMin);
|
|
6948
|
+
const scaledMax = priceScaleTransform(drawState.yMax);
|
|
6949
|
+
return priceScaleUntransform(scaledMin + ratio * (scaledMax - scaledMin));
|
|
6811
6950
|
};
|
|
6812
6951
|
const applyMagnet = (y, index, price) => {
|
|
6813
6952
|
const effective = magnetModifierActive ? "strong" : magnetMode;
|
|
@@ -6879,8 +7018,10 @@ function createChart(element, options = {}) {
|
|
|
6879
7018
|
};
|
|
6880
7019
|
const canvasYFromDrawingPrice = (price) => {
|
|
6881
7020
|
if (!drawState) return 0;
|
|
6882
|
-
const
|
|
6883
|
-
|
|
7021
|
+
const scaledMin = priceScaleTransform(drawState.yMin);
|
|
7022
|
+
const scaledRange = priceScaleTransform(drawState.yMax) - scaledMin || 1;
|
|
7023
|
+
const ratio = (priceScaleTransform(price) - scaledMin) / scaledRange;
|
|
7024
|
+
return priceScaleInverted ? drawState.chartTop + ratio * drawState.chartHeight : drawState.chartBottom - ratio * drawState.chartHeight;
|
|
6884
7025
|
};
|
|
6885
7026
|
const distanceToSegment = (x, y, x1, y1, x2, y2) => {
|
|
6886
7027
|
const dx = x2 - x1;
|
|
@@ -8894,6 +9035,16 @@ function createChart(element, options = {}) {
|
|
|
8894
9035
|
magnetMode = mode;
|
|
8895
9036
|
};
|
|
8896
9037
|
const getMagnetMode = () => magnetMode;
|
|
9038
|
+
const setPriceScale = (options2) => {
|
|
9039
|
+
if (options2.mode === "linear" || options2.mode === "log" || options2.mode === "percent") {
|
|
9040
|
+
priceScaleMode = options2.mode;
|
|
9041
|
+
}
|
|
9042
|
+
if (typeof options2.inverted === "boolean") {
|
|
9043
|
+
priceScaleInverted = options2.inverted;
|
|
9044
|
+
}
|
|
9045
|
+
scheduleDraw();
|
|
9046
|
+
};
|
|
9047
|
+
const getPriceScale = () => ({ mode: priceScaleMode, inverted: priceScaleInverted });
|
|
8897
9048
|
const getDrawings = () => drawings.map((drawing) => serializeDrawing(drawing));
|
|
8898
9049
|
const setDrawings = (nextDrawings) => {
|
|
8899
9050
|
drawings = dedupeDrawingIds(nextDrawings.map((drawing) => normalizeDrawingState(drawing)));
|
|
@@ -8963,6 +9114,7 @@ function createChart(element, options = {}) {
|
|
|
8963
9114
|
drawings: getDrawings(),
|
|
8964
9115
|
indicators: getIndicators(),
|
|
8965
9116
|
magnetMode: getMagnetMode(),
|
|
9117
|
+
priceScale: getPriceScale(),
|
|
8966
9118
|
drawingDefaults
|
|
8967
9119
|
};
|
|
8968
9120
|
};
|
|
@@ -8982,6 +9134,9 @@ function createChart(element, options = {}) {
|
|
|
8982
9134
|
if (state.magnetMode === "none" || state.magnetMode === "weak" || state.magnetMode === "strong") {
|
|
8983
9135
|
setMagnetMode(state.magnetMode);
|
|
8984
9136
|
}
|
|
9137
|
+
if (state.priceScale && typeof state.priceScale === "object") {
|
|
9138
|
+
setPriceScale(state.priceScale);
|
|
9139
|
+
}
|
|
8985
9140
|
if (state.drawingDefaults && typeof state.drawingDefaults === "object") {
|
|
8986
9141
|
for (const [tool, defaults] of Object.entries(state.drawingDefaults)) {
|
|
8987
9142
|
if (defaults && typeof defaults === "object") {
|
|
@@ -9076,6 +9231,8 @@ function createChart(element, options = {}) {
|
|
|
9076
9231
|
onDrawingEditText,
|
|
9077
9232
|
setMagnetMode,
|
|
9078
9233
|
getMagnetMode,
|
|
9234
|
+
setPriceScale,
|
|
9235
|
+
getPriceScale,
|
|
9079
9236
|
cancelDrawing,
|
|
9080
9237
|
setTradeMarkers,
|
|
9081
9238
|
setSelectedDrawing,
|
|
@@ -9101,7 +9258,9 @@ function createChart(element, options = {}) {
|
|
|
9101
9258
|
export {
|
|
9102
9259
|
FIB_DEFAULT_PALETTE,
|
|
9103
9260
|
POSITION_DEFAULT_COLORS,
|
|
9261
|
+
SCRIPT_SOURCE_INPUT_VALUES,
|
|
9104
9262
|
compileScriptIndicator,
|
|
9105
9263
|
createChart,
|
|
9264
|
+
extractScriptInputs,
|
|
9106
9265
|
validateScriptSource
|
|
9107
9266
|
};
|