hyperprop-charting-library 0.1.143 → 0.1.145
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 +1205 -72
- package/dist/hyperprop-charting-library.d.ts +279 -2
- package/dist/hyperprop-charting-library.js +1203 -72
- package/dist/index.cjs +1205 -72
- package/dist/index.d.cts +279 -2
- package/dist/index.d.ts +279 -2
- package/dist/index.js +1203 -72
- package/docs/API.md +171 -1
- package/package.json +3 -2
package/dist/index.js
CHANGED
|
@@ -2743,6 +2743,237 @@ var CHART_THEMES = {
|
|
|
2743
2743
|
var CHART_THEME_NAMES = Object.keys(CHART_THEMES);
|
|
2744
2744
|
var resolveTheme = (theme) => typeof theme === "string" ? CHART_THEMES[theme] ?? CHART_THEMES.midnight : theme;
|
|
2745
2745
|
|
|
2746
|
+
// src/time.ts
|
|
2747
|
+
var DAY_MS = 864e5;
|
|
2748
|
+
var HOUR_MS = 36e5;
|
|
2749
|
+
var OFFSET_FORMATTER_CACHE = /* @__PURE__ */ new Map();
|
|
2750
|
+
var LABEL_FORMATTER_CACHE = /* @__PURE__ */ new Map();
|
|
2751
|
+
var getOffsetFormatter = (timeZone) => {
|
|
2752
|
+
let formatter = OFFSET_FORMATTER_CACHE.get(timeZone);
|
|
2753
|
+
if (!formatter) {
|
|
2754
|
+
formatter = new Intl.DateTimeFormat("en-US", {
|
|
2755
|
+
timeZone,
|
|
2756
|
+
hourCycle: "h23",
|
|
2757
|
+
year: "numeric",
|
|
2758
|
+
month: "2-digit",
|
|
2759
|
+
day: "2-digit",
|
|
2760
|
+
hour: "2-digit",
|
|
2761
|
+
minute: "2-digit",
|
|
2762
|
+
second: "2-digit"
|
|
2763
|
+
});
|
|
2764
|
+
OFFSET_FORMATTER_CACHE.set(timeZone, formatter);
|
|
2765
|
+
}
|
|
2766
|
+
return formatter;
|
|
2767
|
+
};
|
|
2768
|
+
var getLabelFormatter = (timeZone, options, key) => {
|
|
2769
|
+
const cacheKey = `${timeZone ?? "local"}|${key}`;
|
|
2770
|
+
let formatter = LABEL_FORMATTER_CACHE.get(cacheKey);
|
|
2771
|
+
if (!formatter) {
|
|
2772
|
+
formatter = new Intl.DateTimeFormat(void 0, timeZone ? { ...options, timeZone } : options);
|
|
2773
|
+
LABEL_FORMATTER_CACHE.set(cacheKey, formatter);
|
|
2774
|
+
}
|
|
2775
|
+
return formatter;
|
|
2776
|
+
};
|
|
2777
|
+
var isValidTimeZone = (timeZone) => {
|
|
2778
|
+
try {
|
|
2779
|
+
new Intl.DateTimeFormat("en-US", { timeZone });
|
|
2780
|
+
return true;
|
|
2781
|
+
} catch {
|
|
2782
|
+
return false;
|
|
2783
|
+
}
|
|
2784
|
+
};
|
|
2785
|
+
var floorDiv = (value, divisor) => Math.floor(value / divisor);
|
|
2786
|
+
var positiveMod = (value, divisor) => (value % divisor + divisor) % divisor;
|
|
2787
|
+
var computeOffsetMs = (timeZone, ms) => {
|
|
2788
|
+
const parts = getOffsetFormatter(timeZone).formatToParts(new Date(ms));
|
|
2789
|
+
let year = 0;
|
|
2790
|
+
let month = 1;
|
|
2791
|
+
let day = 1;
|
|
2792
|
+
let hour = 0;
|
|
2793
|
+
let minute = 0;
|
|
2794
|
+
let second = 0;
|
|
2795
|
+
for (const part of parts) {
|
|
2796
|
+
const value = Number(part.value);
|
|
2797
|
+
if (part.type === "year") year = value;
|
|
2798
|
+
else if (part.type === "month") month = value;
|
|
2799
|
+
else if (part.type === "day") day = value;
|
|
2800
|
+
else if (part.type === "hour") hour = value === 24 ? 0 : value;
|
|
2801
|
+
else if (part.type === "minute") minute = value;
|
|
2802
|
+
else if (part.type === "second") second = value;
|
|
2803
|
+
}
|
|
2804
|
+
const asUtc = Date.UTC(year, month - 1, day, hour, minute, second);
|
|
2805
|
+
return asUtc - Math.floor(ms / 1e3) * 1e3;
|
|
2806
|
+
};
|
|
2807
|
+
var createZoneClock = (timeZone) => {
|
|
2808
|
+
const normalized = !timeZone || timeZone === "local" ? null : timeZone.toLowerCase() === "utc" ? "UTC" : isValidTimeZone(timeZone) ? timeZone : null;
|
|
2809
|
+
if (normalized === null) {
|
|
2810
|
+
const scratch = /* @__PURE__ */ new Date();
|
|
2811
|
+
const partsOf2 = (ms) => {
|
|
2812
|
+
scratch.setTime(ms);
|
|
2813
|
+
return {
|
|
2814
|
+
year: scratch.getFullYear(),
|
|
2815
|
+
month: scratch.getMonth() + 1,
|
|
2816
|
+
day: scratch.getDate(),
|
|
2817
|
+
hour: scratch.getHours(),
|
|
2818
|
+
minute: scratch.getMinutes()
|
|
2819
|
+
};
|
|
2820
|
+
};
|
|
2821
|
+
return {
|
|
2822
|
+
timeZone: null,
|
|
2823
|
+
offsetMs: (ms) => {
|
|
2824
|
+
scratch.setTime(ms);
|
|
2825
|
+
return -scratch.getTimezoneOffset() * 6e4;
|
|
2826
|
+
},
|
|
2827
|
+
dayKey: (ms) => {
|
|
2828
|
+
scratch.setTime(ms);
|
|
2829
|
+
return floorDiv(Date.UTC(scratch.getFullYear(), scratch.getMonth(), scratch.getDate()), DAY_MS);
|
|
2830
|
+
},
|
|
2831
|
+
minutesOfDay: (ms) => {
|
|
2832
|
+
scratch.setTime(ms);
|
|
2833
|
+
return scratch.getHours() * 60 + scratch.getMinutes();
|
|
2834
|
+
},
|
|
2835
|
+
weekday: (ms) => {
|
|
2836
|
+
scratch.setTime(ms);
|
|
2837
|
+
return scratch.getDay();
|
|
2838
|
+
},
|
|
2839
|
+
parts: partsOf2,
|
|
2840
|
+
formatTime: (ms, hour12) => formatClockTime(partsOf2(ms), hour12),
|
|
2841
|
+
formatDayMonth: (ms) => getLabelFormatter(null, { month: "short", day: "numeric" }, "md").format(ms),
|
|
2842
|
+
formatMonth: (ms) => getLabelFormatter(null, { month: "short" }, "m").format(ms),
|
|
2843
|
+
formatYear: (ms) => String(partsOf2(ms).year)
|
|
2844
|
+
};
|
|
2845
|
+
}
|
|
2846
|
+
const zone = normalized;
|
|
2847
|
+
const offsetCache = /* @__PURE__ */ new Map();
|
|
2848
|
+
const offsetMs = (ms) => {
|
|
2849
|
+
const bucket = floorDiv(ms, HOUR_MS);
|
|
2850
|
+
const cached = offsetCache.get(bucket);
|
|
2851
|
+
if (cached !== void 0) return cached;
|
|
2852
|
+
const offset = computeOffsetMs(zone, ms);
|
|
2853
|
+
if (offsetCache.size > 2e4) offsetCache.clear();
|
|
2854
|
+
offsetCache.set(bucket, offset);
|
|
2855
|
+
return offset;
|
|
2856
|
+
};
|
|
2857
|
+
const localMs = (ms) => ms + offsetMs(ms);
|
|
2858
|
+
const partsOf = (ms) => {
|
|
2859
|
+
const local = new Date(localMs(ms));
|
|
2860
|
+
return {
|
|
2861
|
+
year: local.getUTCFullYear(),
|
|
2862
|
+
month: local.getUTCMonth() + 1,
|
|
2863
|
+
day: local.getUTCDate(),
|
|
2864
|
+
hour: local.getUTCHours(),
|
|
2865
|
+
minute: local.getUTCMinutes()
|
|
2866
|
+
};
|
|
2867
|
+
};
|
|
2868
|
+
return {
|
|
2869
|
+
timeZone: zone,
|
|
2870
|
+
offsetMs,
|
|
2871
|
+
dayKey: (ms) => floorDiv(localMs(ms), DAY_MS),
|
|
2872
|
+
minutesOfDay: (ms) => floorDiv(positiveMod(localMs(ms), DAY_MS), 6e4),
|
|
2873
|
+
// 1970-01-01 was a Thursday (index 4).
|
|
2874
|
+
weekday: (ms) => positiveMod(floorDiv(localMs(ms), DAY_MS) + 4, 7),
|
|
2875
|
+
parts: partsOf,
|
|
2876
|
+
formatTime: (ms, hour12) => formatClockTime(partsOf(ms), hour12),
|
|
2877
|
+
formatDayMonth: (ms) => getLabelFormatter(zone, { month: "short", day: "numeric" }, "md").format(ms),
|
|
2878
|
+
formatMonth: (ms) => getLabelFormatter(zone, { month: "short" }, "m").format(ms),
|
|
2879
|
+
formatYear: (ms) => String(partsOf(ms).year)
|
|
2880
|
+
};
|
|
2881
|
+
};
|
|
2882
|
+
var pad2 = (value) => value < 10 ? `0${value}` : String(value);
|
|
2883
|
+
var formatClockTime = (parts, hour12) => {
|
|
2884
|
+
if (!hour12) return `${pad2(parts.hour)}:${pad2(parts.minute)}`;
|
|
2885
|
+
const suffix = parts.hour < 12 ? "AM" : "PM";
|
|
2886
|
+
const hour = parts.hour % 12 === 0 ? 12 : parts.hour % 12;
|
|
2887
|
+
return `${hour}:${pad2(parts.minute)} ${suffix}`;
|
|
2888
|
+
};
|
|
2889
|
+
var parseHhMm = (value) => {
|
|
2890
|
+
const match = /^(\d{1,2}):?(\d{2})$/.exec(value.trim());
|
|
2891
|
+
if (!match) return null;
|
|
2892
|
+
const hour = Number(match[1]);
|
|
2893
|
+
const minute = Number(match[2]);
|
|
2894
|
+
if (!Number.isFinite(hour) || !Number.isFinite(minute)) return null;
|
|
2895
|
+
if (hour < 0 || hour > 24 || minute < 0 || minute > 59) return null;
|
|
2896
|
+
return hour * 60 + minute;
|
|
2897
|
+
};
|
|
2898
|
+
var SESSION_PRESETS = {
|
|
2899
|
+
"cme-futures": {
|
|
2900
|
+
timezone: "America/New_York",
|
|
2901
|
+
// Opening days: the session opens Sunday–Thursday evening and runs into
|
|
2902
|
+
// the following morning, so Friday evening (weekend) is correctly closed.
|
|
2903
|
+
days: [0, 1, 2, 3, 4],
|
|
2904
|
+
segments: [{ start: "18:00", end: "17:00" }]
|
|
2905
|
+
},
|
|
2906
|
+
"cme-rth": {
|
|
2907
|
+
timezone: "America/New_York",
|
|
2908
|
+
days: [1, 2, 3, 4, 5],
|
|
2909
|
+
segments: [{ start: "09:30", end: "16:00" }]
|
|
2910
|
+
},
|
|
2911
|
+
"us-equities": {
|
|
2912
|
+
timezone: "America/New_York",
|
|
2913
|
+
days: [1, 2, 3, 4, 5],
|
|
2914
|
+
segments: [{ start: "09:30", end: "16:00" }]
|
|
2915
|
+
},
|
|
2916
|
+
"us-equities-extended": {
|
|
2917
|
+
timezone: "America/New_York",
|
|
2918
|
+
days: [1, 2, 3, 4, 5],
|
|
2919
|
+
segments: [{ start: "04:00", end: "20:00" }]
|
|
2920
|
+
},
|
|
2921
|
+
"24x7": {
|
|
2922
|
+
days: [0, 1, 2, 3, 4, 5, 6],
|
|
2923
|
+
segments: [{ start: "00:00", end: "24:00" }]
|
|
2924
|
+
}
|
|
2925
|
+
};
|
|
2926
|
+
var resolveSessionSpec = (spec) => {
|
|
2927
|
+
if (!spec) return null;
|
|
2928
|
+
if (typeof spec === "string") return SESSION_PRESETS[spec] ?? null;
|
|
2929
|
+
return spec;
|
|
2930
|
+
};
|
|
2931
|
+
var compileSession = (spec) => {
|
|
2932
|
+
const resolved = resolveSessionSpec(spec);
|
|
2933
|
+
if (!resolved) return null;
|
|
2934
|
+
const rawSegments = resolved.segments?.length ? resolved.segments : [{ start: "00:00", end: "24:00" }];
|
|
2935
|
+
const segments = [];
|
|
2936
|
+
for (const segment of rawSegments) {
|
|
2937
|
+
const startMinutes = parseHhMm(segment.start);
|
|
2938
|
+
const endMinutesRaw = parseHhMm(segment.end);
|
|
2939
|
+
if (startMinutes === null || endMinutesRaw === null) continue;
|
|
2940
|
+
const endMinutes = endMinutesRaw === 0 ? 1440 : endMinutesRaw;
|
|
2941
|
+
segments.push({ startMinutes, endMinutes, wraps: endMinutes <= startMinutes });
|
|
2942
|
+
}
|
|
2943
|
+
if (segments.length === 0) return null;
|
|
2944
|
+
return {
|
|
2945
|
+
clock: createZoneClock(resolved.timezone ?? null),
|
|
2946
|
+
days: new Set(resolved.days ?? [1, 2, 3, 4, 5]),
|
|
2947
|
+
segments,
|
|
2948
|
+
overnight: segments.some((segment) => segment.wraps)
|
|
2949
|
+
};
|
|
2950
|
+
};
|
|
2951
|
+
var sessionInfoAt = (session, ms) => {
|
|
2952
|
+
const { clock } = session;
|
|
2953
|
+
const dayKey = clock.dayKey(ms);
|
|
2954
|
+
const minutes = clock.minutesOfDay(ms);
|
|
2955
|
+
const weekday = clock.weekday(ms);
|
|
2956
|
+
let sessionDay = dayKey;
|
|
2957
|
+
let inSession = false;
|
|
2958
|
+
for (const segment of session.segments) {
|
|
2959
|
+
if (segment.wraps) {
|
|
2960
|
+
if (minutes >= segment.startMinutes) {
|
|
2961
|
+
if (session.days.has(weekday)) {
|
|
2962
|
+
inSession = true;
|
|
2963
|
+
sessionDay = dayKey + 1;
|
|
2964
|
+
}
|
|
2965
|
+
} else if (minutes < segment.endMinutes) {
|
|
2966
|
+
const openedWeekday = positiveMod(weekday - 1, 7);
|
|
2967
|
+
if (session.days.has(openedWeekday)) inSession = true;
|
|
2968
|
+
sessionDay = dayKey;
|
|
2969
|
+
}
|
|
2970
|
+
} else if (minutes >= segment.startMinutes && minutes < segment.endMinutes && session.days.has(weekday)) {
|
|
2971
|
+
inSession = true;
|
|
2972
|
+
}
|
|
2973
|
+
}
|
|
2974
|
+
return { inSession, sessionDay };
|
|
2975
|
+
};
|
|
2976
|
+
|
|
2746
2977
|
// src/options.ts
|
|
2747
2978
|
var DEFAULT_GRID_OPTIONS = {
|
|
2748
2979
|
color: "#2b2f38",
|
|
@@ -2960,9 +3191,19 @@ var DEFAULT_OPTIONS = {
|
|
|
2960
3191
|
datafeedOptions: { prefetchThresholdBars: 150, cooldownMs: 1200, chunkBars: 1500 },
|
|
2961
3192
|
animation: { viewportTransitions: true, durationMs: 260 },
|
|
2962
3193
|
indicatorUpdate: { liveThrottleMs: 80 },
|
|
3194
|
+
timezone: "local",
|
|
3195
|
+
timeFormat: "24h",
|
|
3196
|
+
session: {
|
|
3197
|
+
spec: null,
|
|
3198
|
+
separators: true,
|
|
3199
|
+
highlightOutOfSession: false,
|
|
3200
|
+
outOfSessionColor: "rgba(120,132,158,0.07)"
|
|
3201
|
+
},
|
|
3202
|
+
history: { enabled: true, limit: 100 },
|
|
2963
3203
|
crosshair: DEFAULT_CROSSHAIR_OPTIONS,
|
|
2964
3204
|
grid: DEFAULT_GRID_OPTIONS,
|
|
2965
3205
|
watermark: DEFAULT_WATERMARK_OPTIONS,
|
|
3206
|
+
alerts: [],
|
|
2966
3207
|
priceLines: [],
|
|
2967
3208
|
orderLines: [],
|
|
2968
3209
|
tickerLine: {
|
|
@@ -3030,6 +3271,14 @@ var mergeChartOptions = (baseOptions, options = {}) => ({
|
|
|
3030
3271
|
...baseOptions.indicatorUpdate,
|
|
3031
3272
|
...options.indicatorUpdate ?? {}
|
|
3032
3273
|
},
|
|
3274
|
+
session: {
|
|
3275
|
+
...baseOptions.session,
|
|
3276
|
+
...options.session ?? {}
|
|
3277
|
+
},
|
|
3278
|
+
history: {
|
|
3279
|
+
...baseOptions.history,
|
|
3280
|
+
...options.history ?? {}
|
|
3281
|
+
},
|
|
3033
3282
|
datafeedOptions: {
|
|
3034
3283
|
...baseOptions.datafeedOptions,
|
|
3035
3284
|
...options.datafeedOptions ?? {}
|
|
@@ -3193,6 +3442,45 @@ function createChart(element, options = {}) {
|
|
|
3193
3442
|
...drawing.label === void 0 ? {} : { label: drawing.label }
|
|
3194
3443
|
});
|
|
3195
3444
|
let tradeMarkers = [];
|
|
3445
|
+
let timezoneSetting = mergedOptions.timezone ?? "local";
|
|
3446
|
+
let zoneClock = createZoneClock(timezoneSetting);
|
|
3447
|
+
let timeFormat = mergedOptions.timeFormat === "12h" ? "12h" : "24h";
|
|
3448
|
+
const DEFAULT_SESSION_OPTIONS = {
|
|
3449
|
+
spec: null,
|
|
3450
|
+
separators: true,
|
|
3451
|
+
highlightOutOfSession: false,
|
|
3452
|
+
outOfSessionColor: "rgba(120,132,158,0.07)"
|
|
3453
|
+
};
|
|
3454
|
+
let sessionOptions = {
|
|
3455
|
+
...DEFAULT_SESSION_OPTIONS,
|
|
3456
|
+
...mergedOptions.session ?? {}
|
|
3457
|
+
};
|
|
3458
|
+
let compiledSession = compileSession(sessionOptions.spec);
|
|
3459
|
+
const isHour12 = () => timeFormat === "12h";
|
|
3460
|
+
const formatClock = (time) => zoneClock.formatTime(time.getTime(), isHour12());
|
|
3461
|
+
let barMarks = [];
|
|
3462
|
+
let timescaleMarks = [];
|
|
3463
|
+
let markRegions = [];
|
|
3464
|
+
let hoveredMarkKey = null;
|
|
3465
|
+
let markClickHandler = null;
|
|
3466
|
+
let markHoverHandler = null;
|
|
3467
|
+
let alerts = [];
|
|
3468
|
+
let generatedAlertId = 1;
|
|
3469
|
+
let alertTriggerHandler = null;
|
|
3470
|
+
let alertActionHandler = null;
|
|
3471
|
+
let alertRegions = [];
|
|
3472
|
+
let alertDragState = null;
|
|
3473
|
+
let hoveredAlertId = null;
|
|
3474
|
+
let replay = null;
|
|
3475
|
+
let replayStateHandler = null;
|
|
3476
|
+
const historyEnabled = mergedOptions.history?.enabled !== false;
|
|
3477
|
+
const historyLimit = Math.max(1, Math.floor(Number(mergedOptions.history?.limit ?? 100)) || 100);
|
|
3478
|
+
let historyPast = [];
|
|
3479
|
+
let historyFuture = [];
|
|
3480
|
+
let historyBaseline = "";
|
|
3481
|
+
let historySuspended = 0;
|
|
3482
|
+
let undoRedoStateHandler = null;
|
|
3483
|
+
let lastUndoRedoSignature = "";
|
|
3196
3484
|
let indicators = (options.indicators ?? []).map((indicator) => normalizeIndicatorState(indicator));
|
|
3197
3485
|
let drawings = dedupeDrawingIds(
|
|
3198
3486
|
(options.drawings ?? []).map((drawing) => normalizeDrawingState(drawing))
|
|
@@ -3241,9 +3529,168 @@ function createChart(element, options = {}) {
|
|
|
3241
3529
|
let selectedDrawingId = null;
|
|
3242
3530
|
const drawingToolDefaults = /* @__PURE__ */ new Map();
|
|
3243
3531
|
const getDrawingToolDefaults = (tool) => drawingToolDefaults.get(tool) ?? {};
|
|
3532
|
+
const historyDoc = () => JSON.stringify(drawings.map((drawing) => serializeDrawing(drawing)));
|
|
3533
|
+
const describeHistoryChange = (beforeDoc, afterDoc) => {
|
|
3534
|
+
let before = [];
|
|
3535
|
+
let after = [];
|
|
3536
|
+
try {
|
|
3537
|
+
before = JSON.parse(beforeDoc);
|
|
3538
|
+
after = JSON.parse(afterDoc);
|
|
3539
|
+
} catch {
|
|
3540
|
+
return "Edit drawings";
|
|
3541
|
+
}
|
|
3542
|
+
const label = (type) => type ? type.replace(/-/g, " ") : "drawing";
|
|
3543
|
+
if (after.length > before.length) {
|
|
3544
|
+
const beforeIds = new Set(before.map((drawing) => drawing.id));
|
|
3545
|
+
const added = after.find((drawing) => !beforeIds.has(drawing.id));
|
|
3546
|
+
return `Add ${label(added?.type)}`;
|
|
3547
|
+
}
|
|
3548
|
+
if (after.length < before.length) {
|
|
3549
|
+
if (after.length === 0 && before.length > 1) return "Clear drawings";
|
|
3550
|
+
const afterIds = new Set(after.map((drawing) => drawing.id));
|
|
3551
|
+
const removed = before.find((drawing) => !afterIds.has(drawing.id));
|
|
3552
|
+
return `Delete ${label(removed?.type)}`;
|
|
3553
|
+
}
|
|
3554
|
+
const changed = after.find((drawing, index) => JSON.stringify(drawing) !== JSON.stringify(before[index]));
|
|
3555
|
+
return `Edit ${label(changed?.type)}`;
|
|
3556
|
+
};
|
|
3557
|
+
const getUndoRedoState = () => ({
|
|
3558
|
+
canUndo: historyPast.length > 0,
|
|
3559
|
+
canRedo: historyFuture.length > 0,
|
|
3560
|
+
undoLabel: historyPast.length > 0 ? historyPast[historyPast.length - 1].label : null,
|
|
3561
|
+
redoLabel: historyFuture.length > 0 ? historyFuture[historyFuture.length - 1].label : null
|
|
3562
|
+
});
|
|
3563
|
+
const emitUndoRedoState = () => {
|
|
3564
|
+
const state = getUndoRedoState();
|
|
3565
|
+
const signature = `${state.canUndo}|${state.canRedo}|${state.undoLabel}|${state.redoLabel}`;
|
|
3566
|
+
if (signature === lastUndoRedoSignature) return;
|
|
3567
|
+
lastUndoRedoSignature = signature;
|
|
3568
|
+
undoRedoStateHandler?.(state);
|
|
3569
|
+
};
|
|
3570
|
+
const commitHistory = () => {
|
|
3571
|
+
if (!historyEnabled || historySuspended > 0) return;
|
|
3572
|
+
if (drawingDragState !== null) return;
|
|
3573
|
+
const next = historyDoc();
|
|
3574
|
+
if (next === historyBaseline) return;
|
|
3575
|
+
historyPast.push({ label: describeHistoryChange(historyBaseline, next), doc: historyBaseline });
|
|
3576
|
+
if (historyPast.length > historyLimit) historyPast.shift();
|
|
3577
|
+
historyFuture = [];
|
|
3578
|
+
historyBaseline = next;
|
|
3579
|
+
emitUndoRedoState();
|
|
3580
|
+
};
|
|
3581
|
+
const resetHistoryBaseline = () => {
|
|
3582
|
+
historyBaseline = historyDoc();
|
|
3583
|
+
};
|
|
3584
|
+
const applyHistoryDoc = (doc) => {
|
|
3585
|
+
let parsed;
|
|
3586
|
+
try {
|
|
3587
|
+
parsed = JSON.parse(doc);
|
|
3588
|
+
} catch {
|
|
3589
|
+
return;
|
|
3590
|
+
}
|
|
3591
|
+
historySuspended += 1;
|
|
3592
|
+
try {
|
|
3593
|
+
drawings = dedupeDrawingIds(parsed.map((drawing) => normalizeDrawingState(drawing)));
|
|
3594
|
+
if (selectedDrawingId !== null && !drawings.some((drawing) => drawing.id === selectedDrawingId)) {
|
|
3595
|
+
selectedDrawingId = null;
|
|
3596
|
+
}
|
|
3597
|
+
draftDrawing = null;
|
|
3598
|
+
drawingDragState = null;
|
|
3599
|
+
historyBaseline = doc;
|
|
3600
|
+
emitDrawingsChange();
|
|
3601
|
+
scheduleDraw({ updateAutoScale: true });
|
|
3602
|
+
} finally {
|
|
3603
|
+
historySuspended -= 1;
|
|
3604
|
+
}
|
|
3605
|
+
};
|
|
3606
|
+
const undo = () => {
|
|
3607
|
+
if (!historyEnabled || historyPast.length === 0) return false;
|
|
3608
|
+
const entry = historyPast.pop();
|
|
3609
|
+
historyFuture.push({ label: entry.label, doc: historyBaseline });
|
|
3610
|
+
applyHistoryDoc(entry.doc);
|
|
3611
|
+
emitUndoRedoState();
|
|
3612
|
+
return true;
|
|
3613
|
+
};
|
|
3614
|
+
const redo = () => {
|
|
3615
|
+
if (!historyEnabled || historyFuture.length === 0) return false;
|
|
3616
|
+
const entry = historyFuture.pop();
|
|
3617
|
+
historyPast.push({ label: entry.label, doc: historyBaseline });
|
|
3618
|
+
applyHistoryDoc(entry.doc);
|
|
3619
|
+
emitUndoRedoState();
|
|
3620
|
+
return true;
|
|
3621
|
+
};
|
|
3622
|
+
const clearHistory = () => {
|
|
3623
|
+
historyPast = [];
|
|
3624
|
+
historyFuture = [];
|
|
3625
|
+
resetHistoryBaseline();
|
|
3626
|
+
emitUndoRedoState();
|
|
3627
|
+
};
|
|
3244
3628
|
const emitDrawingsChange = () => {
|
|
3629
|
+
commitHistory();
|
|
3245
3630
|
drawingsChangeHandler?.(drawings.map((drawing) => serializeDrawing(drawing)));
|
|
3246
3631
|
};
|
|
3632
|
+
const normalizeAlert = (alert) => ({
|
|
3633
|
+
id: alert.id ?? `alert-${generatedAlertId++}`,
|
|
3634
|
+
price: Number(alert.price),
|
|
3635
|
+
condition: alert.condition ?? "crossing",
|
|
3636
|
+
label: alert.label ?? "",
|
|
3637
|
+
color: alert.color ?? "#f7a600",
|
|
3638
|
+
active: alert.active !== false,
|
|
3639
|
+
triggered: alert.triggered === true,
|
|
3640
|
+
once: alert.once !== false,
|
|
3641
|
+
draggable: alert.draggable !== false,
|
|
3642
|
+
lastPrice: null
|
|
3643
|
+
});
|
|
3644
|
+
const serializeAlert = (alert) => ({
|
|
3645
|
+
id: alert.id,
|
|
3646
|
+
price: alert.price,
|
|
3647
|
+
condition: alert.condition,
|
|
3648
|
+
label: alert.label,
|
|
3649
|
+
color: alert.color,
|
|
3650
|
+
active: alert.active,
|
|
3651
|
+
triggered: alert.triggered,
|
|
3652
|
+
once: alert.once,
|
|
3653
|
+
draggable: alert.draggable
|
|
3654
|
+
});
|
|
3655
|
+
const evaluateAlerts = (price, timeMs, index) => {
|
|
3656
|
+
if (alerts.length === 0 || !Number.isFinite(price)) return;
|
|
3657
|
+
for (const alert of alerts) {
|
|
3658
|
+
if (!alert.active || alert.triggered || !Number.isFinite(alert.price)) {
|
|
3659
|
+
alert.lastPrice = price;
|
|
3660
|
+
continue;
|
|
3661
|
+
}
|
|
3662
|
+
const previous = alert.lastPrice;
|
|
3663
|
+
let hit = false;
|
|
3664
|
+
switch (alert.condition) {
|
|
3665
|
+
case "greater":
|
|
3666
|
+
hit = price > alert.price;
|
|
3667
|
+
break;
|
|
3668
|
+
case "less":
|
|
3669
|
+
hit = price < alert.price;
|
|
3670
|
+
break;
|
|
3671
|
+
case "crossing-up":
|
|
3672
|
+
hit = previous !== null && previous < alert.price && price >= alert.price;
|
|
3673
|
+
break;
|
|
3674
|
+
case "crossing-down":
|
|
3675
|
+
hit = previous !== null && previous > alert.price && price <= alert.price;
|
|
3676
|
+
break;
|
|
3677
|
+
default:
|
|
3678
|
+
hit = previous !== null && (previous < alert.price && price >= alert.price || previous > alert.price && price <= alert.price);
|
|
3679
|
+
break;
|
|
3680
|
+
}
|
|
3681
|
+
alert.lastPrice = price;
|
|
3682
|
+
if (!hit) continue;
|
|
3683
|
+
alert.triggered = true;
|
|
3684
|
+
if (alert.once) alert.active = false;
|
|
3685
|
+
alertTriggerHandler?.({ alert: serializeAlert(alert), price, timeMs, index });
|
|
3686
|
+
scheduleDraw({ overlayOnly: false });
|
|
3687
|
+
}
|
|
3688
|
+
};
|
|
3689
|
+
const evaluateAlertsFromLatestBar = () => {
|
|
3690
|
+
if (alerts.length === 0 || data.length === 0) return;
|
|
3691
|
+
const last = data[data.length - 1];
|
|
3692
|
+
evaluateAlerts(last.c, last.time.getTime(), data.length - 1);
|
|
3693
|
+
};
|
|
3247
3694
|
const orderWidgetWidthById = /* @__PURE__ */ new Map();
|
|
3248
3695
|
const orderPriceTagWidthById = /* @__PURE__ */ new Map();
|
|
3249
3696
|
let xCenter = 0;
|
|
@@ -3416,6 +3863,9 @@ function createChart(element, options = {}) {
|
|
|
3416
3863
|
let overlayLayout = null;
|
|
3417
3864
|
const margin = { top: 16, right: 72, bottom: 26, left: 12 };
|
|
3418
3865
|
let cachedRightMargin = margin.right;
|
|
3866
|
+
let rightMarginShrinkPendingSince = null;
|
|
3867
|
+
const RIGHT_MARGIN_SHRINK_DELAY_MS = 600;
|
|
3868
|
+
const RIGHT_MARGIN_SHRINK_EPSILON_PX = 2;
|
|
3419
3869
|
const minVisibleBars = Math.max(1, Math.floor(mergedOptions.minVisibleBars));
|
|
3420
3870
|
const maxVisibleBars = Math.max(minVisibleBars, Math.floor(mergedOptions.maxVisibleBars));
|
|
3421
3871
|
const maxPanBars = Math.max(0, Math.floor(mergedOptions.maxPanBars));
|
|
@@ -3743,6 +4193,7 @@ function createChart(element, options = {}) {
|
|
|
3743
4193
|
};
|
|
3744
4194
|
const resetRightMarginCache = () => {
|
|
3745
4195
|
cachedRightMargin = margin.right;
|
|
4196
|
+
rightMarginShrinkPendingSince = null;
|
|
3746
4197
|
};
|
|
3747
4198
|
const parseData = (nextData) => {
|
|
3748
4199
|
const dedupedByTime = /* @__PURE__ */ new Map();
|
|
@@ -4105,40 +4556,13 @@ function createChart(element, options = {}) {
|
|
|
4105
4556
|
datafeedUnsubscribe = typeof unsubscribe === "function" ? unsubscribe : null;
|
|
4106
4557
|
};
|
|
4107
4558
|
const formatHoverTimeLabel = (time, mode) => {
|
|
4108
|
-
|
|
4109
|
-
|
|
4110
|
-
|
|
4111
|
-
|
|
4112
|
-
hour12: false
|
|
4113
|
-
});
|
|
4114
|
-
}
|
|
4115
|
-
if (mode === "datetime") {
|
|
4116
|
-
return time.toLocaleString(void 0, {
|
|
4117
|
-
month: "short",
|
|
4118
|
-
day: "numeric",
|
|
4119
|
-
hour: "2-digit",
|
|
4120
|
-
minute: "2-digit",
|
|
4121
|
-
hour12: false
|
|
4122
|
-
});
|
|
4123
|
-
}
|
|
4124
|
-
if (mode === "date") {
|
|
4125
|
-
return time.toLocaleDateString(void 0, {
|
|
4126
|
-
month: "short",
|
|
4127
|
-
day: "numeric"
|
|
4128
|
-
});
|
|
4129
|
-
}
|
|
4559
|
+
const ms = time.getTime();
|
|
4560
|
+
if (mode === "time") return formatClock(time);
|
|
4561
|
+
if (mode === "datetime") return `${zoneClock.formatDayMonth(ms)}, ${formatClock(time)}`;
|
|
4562
|
+
if (mode === "date") return zoneClock.formatDayMonth(ms);
|
|
4130
4563
|
const stepMs = getTimeStepMs();
|
|
4131
|
-
if (stepMs < 24 * 60 * 60 * 1e3)
|
|
4132
|
-
|
|
4133
|
-
hour: "2-digit",
|
|
4134
|
-
minute: "2-digit",
|
|
4135
|
-
hour12: false
|
|
4136
|
-
});
|
|
4137
|
-
}
|
|
4138
|
-
return time.toLocaleDateString(void 0, {
|
|
4139
|
-
month: "short",
|
|
4140
|
-
day: "numeric"
|
|
4141
|
-
});
|
|
4564
|
+
if (stepMs < 24 * 60 * 60 * 1e3) return formatClock(time);
|
|
4565
|
+
return zoneClock.formatDayMonth(ms);
|
|
4142
4566
|
};
|
|
4143
4567
|
const drawText = (text, x, y, align = "left", baseline = "alphabetic", color = mergedOptions.axis?.textColor ?? mergedOptions.axisColor) => {
|
|
4144
4568
|
ctx.fillStyle = color;
|
|
@@ -4235,6 +4659,86 @@ function createChart(element, options = {}) {
|
|
|
4235
4659
|
labelTextColorOn(mergedLine.labelBackgroundColor, mergedLine.labelTextColor)
|
|
4236
4660
|
);
|
|
4237
4661
|
};
|
|
4662
|
+
const drawBellGlyph = (x, y, size, color) => {
|
|
4663
|
+
const w = size;
|
|
4664
|
+
const h = size;
|
|
4665
|
+
ctx.save();
|
|
4666
|
+
ctx.strokeStyle = color;
|
|
4667
|
+
ctx.fillStyle = color;
|
|
4668
|
+
ctx.lineWidth = 1.2;
|
|
4669
|
+
ctx.lineJoin = "round";
|
|
4670
|
+
ctx.beginPath();
|
|
4671
|
+
ctx.moveTo(x - w * 0.42, y + h * 0.28);
|
|
4672
|
+
ctx.quadraticCurveTo(x - w * 0.3, y + h * 0.2, x - w * 0.3, y - h * 0.05);
|
|
4673
|
+
ctx.quadraticCurveTo(x - w * 0.3, y - h * 0.46, x, y - h * 0.46);
|
|
4674
|
+
ctx.quadraticCurveTo(x + w * 0.3, y - h * 0.46, x + w * 0.3, y - h * 0.05);
|
|
4675
|
+
ctx.quadraticCurveTo(x + w * 0.3, y + h * 0.2, x + w * 0.42, y + h * 0.28);
|
|
4676
|
+
ctx.closePath();
|
|
4677
|
+
ctx.stroke();
|
|
4678
|
+
ctx.beginPath();
|
|
4679
|
+
ctx.arc(x, y + h * 0.42, Math.max(1, w * 0.1), 0, Math.PI * 2);
|
|
4680
|
+
ctx.fill();
|
|
4681
|
+
ctx.restore();
|
|
4682
|
+
};
|
|
4683
|
+
const drawAlertLine = (alert, yFromPrice, chartLeft, chartTop, chartRight, chartBottom) => {
|
|
4684
|
+
const lineY = getLineY(alert.price, yFromPrice, chartTop, chartBottom, false);
|
|
4685
|
+
if (lineY === null) return;
|
|
4686
|
+
const dim = alert.triggered || !alert.active;
|
|
4687
|
+
const color = alert.color || "#f7a600";
|
|
4688
|
+
const axis = resolvedAxis;
|
|
4689
|
+
ctx.save();
|
|
4690
|
+
ctx.globalAlpha = dim ? 0.5 : 1;
|
|
4691
|
+
ctx.strokeStyle = color;
|
|
4692
|
+
ctx.lineWidth = 1;
|
|
4693
|
+
applyDashPattern("dashed", dashPatterns.dotted, dashPatterns.dashed);
|
|
4694
|
+
ctx.beginPath();
|
|
4695
|
+
ctx.moveTo(crisp(chartLeft), crisp(lineY));
|
|
4696
|
+
ctx.lineTo(crisp(chartRight), crisp(lineY));
|
|
4697
|
+
ctx.stroke();
|
|
4698
|
+
ctx.restore();
|
|
4699
|
+
const fontSize = Math.max(8, axis.fontSize);
|
|
4700
|
+
ctx.font = `${fontSize}px ${mergedOptions.fontFamily}`;
|
|
4701
|
+
const text = alert.label ? `${formatPrice(alert.price)} ${alert.label}` : formatPrice(alert.price);
|
|
4702
|
+
const paddingX = 6;
|
|
4703
|
+
const bellWidth = fontSize + 2;
|
|
4704
|
+
const labelHeight = 20;
|
|
4705
|
+
const hovered = hoveredAlertId === alert.id;
|
|
4706
|
+
const removeWidth = hovered ? labelHeight : 0;
|
|
4707
|
+
const contentWidth = Math.ceil(measureTextWidth(text)) + bellWidth + paddingX * 3 + removeWidth;
|
|
4708
|
+
const labelWidth = getRightAxisLabelWidth(chartRight, contentWidth);
|
|
4709
|
+
const labelX = getRightAxisLabelX(chartRight);
|
|
4710
|
+
const labelY = placeRightAxisLabel(lineY - labelHeight / 2, labelHeight, chartTop, chartBottom - labelHeight);
|
|
4711
|
+
ctx.save();
|
|
4712
|
+
ctx.globalAlpha = dim ? 0.6 : 1;
|
|
4713
|
+
ctx.fillStyle = color;
|
|
4714
|
+
fillRoundedRect(Math.round(labelX), Math.round(labelY), labelWidth, labelHeight, 3);
|
|
4715
|
+
const inkColor = labelTextColorOn(color, "#101114");
|
|
4716
|
+
drawBellGlyph(labelX + paddingX + bellWidth / 2, labelY + labelHeight / 2, fontSize, inkColor);
|
|
4717
|
+
drawText(text, labelX + paddingX * 2 + bellWidth, labelY + labelHeight / 2, "left", "middle", inkColor);
|
|
4718
|
+
if (hovered) {
|
|
4719
|
+
const removeX = labelX + labelWidth - removeWidth;
|
|
4720
|
+
ctx.strokeStyle = inkColor;
|
|
4721
|
+
ctx.lineWidth = 1.4;
|
|
4722
|
+
const inset = 6;
|
|
4723
|
+
ctx.beginPath();
|
|
4724
|
+
ctx.moveTo(removeX + inset, labelY + inset);
|
|
4725
|
+
ctx.lineTo(removeX + removeWidth - inset, labelY + labelHeight - inset);
|
|
4726
|
+
ctx.moveTo(removeX + removeWidth - inset, labelY + inset);
|
|
4727
|
+
ctx.lineTo(removeX + inset, labelY + labelHeight - inset);
|
|
4728
|
+
ctx.stroke();
|
|
4729
|
+
alertRegions.push({
|
|
4730
|
+
id: alert.id,
|
|
4731
|
+
x: removeX,
|
|
4732
|
+
y: labelY,
|
|
4733
|
+
width: removeWidth,
|
|
4734
|
+
height: labelHeight,
|
|
4735
|
+
part: "remove"
|
|
4736
|
+
});
|
|
4737
|
+
}
|
|
4738
|
+
ctx.restore();
|
|
4739
|
+
alertRegions.push({ id: alert.id, x: labelX, y: labelY, width: labelWidth - removeWidth, height: labelHeight, part: "line" });
|
|
4740
|
+
alertRegions.push({ id: alert.id, x: chartLeft, y: lineY - 5, width: chartRight - chartLeft, height: 10, part: "line" });
|
|
4741
|
+
};
|
|
4238
4742
|
const drawOrderLine = (line, yFromPrice, chartLeft, chartTop, chartRight, chartBottom) => {
|
|
4239
4743
|
const mergedLine = { ...DEFAULT_ORDER_LINE_OPTIONS, ...line };
|
|
4240
4744
|
const renderPrice = mergedLine.behavior === "follow" && Number.isFinite(mergedLine.followPrice) ? mergedLine.followPrice : mergedLine.price;
|
|
@@ -4519,6 +5023,37 @@ function createChart(element, options = {}) {
|
|
|
4519
5023
|
ctx.closePath();
|
|
4520
5024
|
ctx.stroke();
|
|
4521
5025
|
};
|
|
5026
|
+
const drawMarkBadge = (x, y, radius, color, shape, label, textColor) => {
|
|
5027
|
+
ctx.save();
|
|
5028
|
+
ctx.fillStyle = color;
|
|
5029
|
+
ctx.beginPath();
|
|
5030
|
+
if (shape === "square") {
|
|
5031
|
+
ctx.rect(x - radius, y - radius, radius * 2, radius * 2);
|
|
5032
|
+
} else if (shape === "diamond") {
|
|
5033
|
+
ctx.moveTo(x, y - radius);
|
|
5034
|
+
ctx.lineTo(x + radius, y);
|
|
5035
|
+
ctx.lineTo(x, y + radius);
|
|
5036
|
+
ctx.lineTo(x - radius, y);
|
|
5037
|
+
ctx.closePath();
|
|
5038
|
+
} else if (shape === "flag") {
|
|
5039
|
+
ctx.rect(x - radius * 0.16, y - radius, radius * 0.32, radius * 2);
|
|
5040
|
+
ctx.moveTo(x + radius * 0.16, y - radius);
|
|
5041
|
+
ctx.lineTo(x + radius * 1.35, y - radius * 0.45);
|
|
5042
|
+
ctx.lineTo(x + radius * 0.16, y + radius * 0.1);
|
|
5043
|
+
ctx.closePath();
|
|
5044
|
+
} else {
|
|
5045
|
+
ctx.arc(x, y, radius, 0, Math.PI * 2);
|
|
5046
|
+
}
|
|
5047
|
+
ctx.fill();
|
|
5048
|
+
if (label && shape !== "flag") {
|
|
5049
|
+
ctx.fillStyle = textColor;
|
|
5050
|
+
ctx.font = `600 ${Math.max(8, Math.round(radius * 1.2))}px ${mergedOptions.fontFamily}`;
|
|
5051
|
+
ctx.textAlign = "center";
|
|
5052
|
+
ctx.textBaseline = "middle";
|
|
5053
|
+
ctx.fillText(label.slice(0, 2), x, y + 0.5);
|
|
5054
|
+
}
|
|
5055
|
+
ctx.restore();
|
|
5056
|
+
};
|
|
4522
5057
|
const setCrosshairPoint = (point) => {
|
|
4523
5058
|
const current = crosshairPoint;
|
|
4524
5059
|
if (point === null && current === null) {
|
|
@@ -4566,6 +5101,8 @@ function createChart(element, options = {}) {
|
|
|
4566
5101
|
const shouldUpdateAutoScale = options2.updateAutoScale ?? true;
|
|
4567
5102
|
orderActionRegions = [];
|
|
4568
5103
|
orderDragRegions = [];
|
|
5104
|
+
alertRegions = [];
|
|
5105
|
+
markRegions = [];
|
|
4569
5106
|
crosshairPriceActionRegion = null;
|
|
4570
5107
|
const pixelRatio = getPixelRatio();
|
|
4571
5108
|
canvas.style.width = `${width}px`;
|
|
@@ -4648,7 +5185,23 @@ function createChart(element, options = {}) {
|
|
|
4648
5185
|
}
|
|
4649
5186
|
const maxRightMargin = Math.max(margin.right, width - margin.left - 160);
|
|
4650
5187
|
const nextRightMargin = Math.min(maxRightMargin, Math.max(margin.right, Math.ceil(required + 1)));
|
|
4651
|
-
|
|
5188
|
+
if (nextRightMargin >= cachedRightMargin) {
|
|
5189
|
+
cachedRightMargin = nextRightMargin;
|
|
5190
|
+
rightMarginShrinkPendingSince = null;
|
|
5191
|
+
} else if (cachedRightMargin - nextRightMargin > RIGHT_MARGIN_SHRINK_EPSILON_PX) {
|
|
5192
|
+
const now = performance.now();
|
|
5193
|
+
if (rightMarginShrinkPendingSince === null) {
|
|
5194
|
+
rightMarginShrinkPendingSince = now;
|
|
5195
|
+
}
|
|
5196
|
+
if (now - rightMarginShrinkPendingSince >= RIGHT_MARGIN_SHRINK_DELAY_MS) {
|
|
5197
|
+
cachedRightMargin = nextRightMargin;
|
|
5198
|
+
rightMarginShrinkPendingSince = null;
|
|
5199
|
+
} else {
|
|
5200
|
+
scheduleDraw({ updateAutoScale: false });
|
|
5201
|
+
}
|
|
5202
|
+
} else {
|
|
5203
|
+
rightMarginShrinkPendingSince = null;
|
|
5204
|
+
}
|
|
4652
5205
|
return Math.min(maxRightMargin, cachedRightMargin);
|
|
4653
5206
|
};
|
|
4654
5207
|
const rightMargin = estimateRightMargin();
|
|
@@ -5821,12 +6374,14 @@ function createChart(element, options = {}) {
|
|
|
5821
6374
|
if (!prevTime) {
|
|
5822
6375
|
weight = 7;
|
|
5823
6376
|
} else {
|
|
5824
|
-
|
|
5825
|
-
|
|
5826
|
-
|
|
6377
|
+
const prev = zoneClock.parts(prevTime.getTime());
|
|
6378
|
+
const current = zoneClock.parts(time.getTime());
|
|
6379
|
+
if (prev.year !== current.year) weight = 9;
|
|
6380
|
+
else if (prev.month !== current.month) weight = 8;
|
|
6381
|
+
else if (prev.day !== current.day) weight = 7;
|
|
5827
6382
|
else {
|
|
5828
|
-
const prevMinutes =
|
|
5829
|
-
const curMinutes =
|
|
6383
|
+
const prevMinutes = prev.hour * 60 + prev.minute;
|
|
6384
|
+
const curMinutes = current.hour * 60 + current.minute;
|
|
5830
6385
|
const crossed = (span) => Math.floor(prevMinutes / span) !== Math.floor(curMinutes / span);
|
|
5831
6386
|
if (crossed(360)) weight = 6;
|
|
5832
6387
|
else if (crossed(60)) weight = 5;
|
|
@@ -5872,31 +6427,64 @@ function createChart(element, options = {}) {
|
|
|
5872
6427
|
ctx.stroke();
|
|
5873
6428
|
ctx.restore();
|
|
5874
6429
|
}
|
|
5875
|
-
|
|
6430
|
+
const sessionSeparatorsOn = grid.sessionSeparators || compiledSession !== null && sessionOptions.separators !== false;
|
|
6431
|
+
const intradaySeries = (() => {
|
|
6432
|
+
if (data.length < 2) return false;
|
|
6433
|
+
const probe = Math.min(Math.max(1, startIndex), data.length - 1);
|
|
6434
|
+
const delta = Math.abs(data[probe].time.getTime() - data[probe - 1].time.getTime());
|
|
6435
|
+
return delta > 0 && delta < 0.9 * 864e5;
|
|
6436
|
+
})();
|
|
6437
|
+
if (compiledSession && sessionOptions.highlightOutOfSession && intradaySeries && data.length > 0) {
|
|
6438
|
+
const session = compiledSession;
|
|
6439
|
+
ctx.save();
|
|
6440
|
+
ctx.fillStyle = sessionOptions.outOfSessionColor;
|
|
6441
|
+
let runStart = null;
|
|
6442
|
+
const flushRun = (endExclusive) => {
|
|
6443
|
+
if (runStart === null) return;
|
|
6444
|
+
const left = chartLeft + (runStart - xStart) / xSpan * chartWidth;
|
|
6445
|
+
const right = chartLeft + (endExclusive - xStart) / xSpan * chartWidth;
|
|
6446
|
+
const clampedLeft = Math.max(chartLeft, left);
|
|
6447
|
+
const clampedRight = Math.min(chartRight, right);
|
|
6448
|
+
if (clampedRight > clampedLeft) {
|
|
6449
|
+
ctx.fillRect(clampedLeft, chartTop, clampedRight - clampedLeft, fullChartBottom - chartTop);
|
|
6450
|
+
}
|
|
6451
|
+
runStart = null;
|
|
6452
|
+
};
|
|
6453
|
+
for (let index = Math.max(0, startIndex); index <= endIndex; index += 1) {
|
|
6454
|
+
const point = data[index];
|
|
6455
|
+
if (!point) continue;
|
|
6456
|
+
const inSession = sessionInfoAt(session, point.time.getTime()).inSession;
|
|
6457
|
+
if (!inSession) {
|
|
6458
|
+
if (runStart === null) runStart = index;
|
|
6459
|
+
} else {
|
|
6460
|
+
flushRun(index);
|
|
6461
|
+
}
|
|
6462
|
+
}
|
|
6463
|
+
flushRun(endIndex + 1);
|
|
6464
|
+
ctx.restore();
|
|
6465
|
+
}
|
|
6466
|
+
if (sessionSeparatorsOn && data.length > 1 && intradaySeries) {
|
|
5876
6467
|
const first = Math.max(1, startIndex);
|
|
5877
|
-
const
|
|
5878
|
-
|
|
5879
|
-
|
|
5880
|
-
|
|
5881
|
-
|
|
5882
|
-
|
|
5883
|
-
|
|
5884
|
-
|
|
5885
|
-
|
|
5886
|
-
|
|
5887
|
-
|
|
5888
|
-
|
|
5889
|
-
if (sessionDayOf(point.time.getTime()) === sessionDayOf(prev.time.getTime())) {
|
|
5890
|
-
continue;
|
|
5891
|
-
}
|
|
5892
|
-
const x = chartLeft + (index - xStart) / xSpan * chartWidth;
|
|
5893
|
-
ctx.beginPath();
|
|
5894
|
-
ctx.moveTo(crisp(x), crisp(chartTop));
|
|
5895
|
-
ctx.lineTo(crisp(x), crisp(fullChartBottom));
|
|
5896
|
-
ctx.stroke();
|
|
6468
|
+
const session = compiledSession;
|
|
6469
|
+
const dayOf = (ms) => session ? sessionInfoAt(session, ms).sessionDay : zoneClock.dayKey(ms);
|
|
6470
|
+
ctx.save();
|
|
6471
|
+
ctx.globalAlpha = clamp(grid.sessionSeparatorOpacity, 0, 1);
|
|
6472
|
+
ctx.strokeStyle = grid.sessionSeparatorColor;
|
|
6473
|
+
ctx.lineWidth = 1;
|
|
6474
|
+
for (let index = first; index <= endIndex; index += 1) {
|
|
6475
|
+
const point = data[index];
|
|
6476
|
+
const prev = data[index - 1];
|
|
6477
|
+
if (!point || !prev) continue;
|
|
6478
|
+
if (dayOf(point.time.getTime()) === dayOf(prev.time.getTime())) {
|
|
6479
|
+
continue;
|
|
5897
6480
|
}
|
|
5898
|
-
|
|
6481
|
+
const x = chartLeft + (index - xStart) / xSpan * chartWidth;
|
|
6482
|
+
ctx.beginPath();
|
|
6483
|
+
ctx.moveTo(crisp(x), crisp(chartTop));
|
|
6484
|
+
ctx.lineTo(crisp(x), crisp(fullChartBottom));
|
|
6485
|
+
ctx.stroke();
|
|
5899
6486
|
}
|
|
6487
|
+
ctx.restore();
|
|
5900
6488
|
}
|
|
5901
6489
|
const tickerOpts = mergedOptions.tickerLine ?? DEFAULT_OPTIONS.tickerLine;
|
|
5902
6490
|
const useSmoothedCandle = tickerOpts.smoothing && smoothedTickerPrice !== null;
|
|
@@ -6477,6 +7065,35 @@ function createChart(element, options = {}) {
|
|
|
6477
7065
|
ctx.textAlign = prevAlign;
|
|
6478
7066
|
ctx.textBaseline = prevBaseline;
|
|
6479
7067
|
}
|
|
7068
|
+
if (barMarks.length > 0 && data.length > 0) {
|
|
7069
|
+
const prevFont = ctx.font;
|
|
7070
|
+
const prevAlign = ctx.textAlign;
|
|
7071
|
+
const prevBaseline = ctx.textBaseline;
|
|
7072
|
+
ctx.textAlign = "center";
|
|
7073
|
+
ctx.textBaseline = "middle";
|
|
7074
|
+
for (const mark of barMarks) {
|
|
7075
|
+
const ms = typeof mark.time === "number" ? mark.time : Date.parse(String(mark.time));
|
|
7076
|
+
if (!Number.isFinite(ms)) continue;
|
|
7077
|
+
const index = findNearestIndexForTimeMs(ms);
|
|
7078
|
+
if (index === null) continue;
|
|
7079
|
+
const bar = data[index];
|
|
7080
|
+
if (!bar) continue;
|
|
7081
|
+
const x = chartLeft + (index + 0.5 - xStart) / xSpan * chartWidth;
|
|
7082
|
+
if (x < chartLeft - 20 || x > chartRight + 20) continue;
|
|
7083
|
+
const radius = Math.max(4, Number(mark.size) || 7);
|
|
7084
|
+
const above = mark.position === "above";
|
|
7085
|
+
const y = Number.isFinite(mark.price) ? yFromPrice(Number(mark.price)) : above ? yFromPrice(bar.h) - radius - 8 : yFromPrice(bar.l) + radius + 8;
|
|
7086
|
+
if (y < chartTop - radius || y > chartBottom + radius) continue;
|
|
7087
|
+
const key = mark.id ?? `bar:${index}:${mark.label ?? ""}`;
|
|
7088
|
+
const hovered = hoveredMarkKey === `bar|${key}`;
|
|
7089
|
+
const color = mark.color ?? "#f7a600";
|
|
7090
|
+
drawMarkBadge(x, y, hovered ? radius + 1.5 : radius, color, mark.shape ?? "circle", mark.label ?? "", mark.textColor ?? "#101114");
|
|
7091
|
+
markRegions.push({ kind: "bar", mark, id: key, x, y, radius: radius + 3 });
|
|
7092
|
+
}
|
|
7093
|
+
ctx.font = prevFont;
|
|
7094
|
+
ctx.textAlign = prevAlign;
|
|
7095
|
+
ctx.textBaseline = prevBaseline;
|
|
7096
|
+
}
|
|
6480
7097
|
ctx.restore();
|
|
6481
7098
|
const positionAxisGutter = Math.max(0, width - chartRight);
|
|
6482
7099
|
if (positionAxisGutter > 0) {
|
|
@@ -6987,6 +7604,9 @@ function createChart(element, options = {}) {
|
|
|
6987
7604
|
for (const priceLine of priceLines) {
|
|
6988
7605
|
drawPriceLine(priceLine, yFromPrice, chartLeft, chartTop, chartRight, chartBottom);
|
|
6989
7606
|
}
|
|
7607
|
+
for (const alert of alerts) {
|
|
7608
|
+
drawAlertLine(alert, yFromPrice, chartLeft, chartTop, chartRight, chartBottom);
|
|
7609
|
+
}
|
|
6990
7610
|
for (const orderLine of orderLines) {
|
|
6991
7611
|
drawOrderLine(orderLine, yFromPrice, chartLeft, chartTop, chartRight, chartBottom);
|
|
6992
7612
|
}
|
|
@@ -7096,11 +7716,36 @@ function createChart(element, options = {}) {
|
|
|
7096
7716
|
for (const tick of timeTicks) {
|
|
7097
7717
|
const tickTime = getTimeForIndex(tick.index);
|
|
7098
7718
|
if (!tickTime) continue;
|
|
7099
|
-
const
|
|
7719
|
+
const tickMs = tickTime.getTime();
|
|
7720
|
+
const timeLabel = tick.weight >= 9 ? zoneClock.formatYear(tickMs) : tick.weight === 8 ? zoneClock.formatMonth(tickMs) : tick.weight === 7 ? zoneClock.formatDayMonth(tickMs) : formatClock(tickTime);
|
|
7100
7721
|
drawText(timeLabel, tick.x, (fullChartBottom + height) / 2, "center", "middle", xAxis.textColor);
|
|
7101
7722
|
}
|
|
7102
7723
|
ctx.font = prevFont;
|
|
7103
7724
|
}
|
|
7725
|
+
if (timescaleMarks.length > 0 && data.length > 0) {
|
|
7726
|
+
const radius = 6;
|
|
7727
|
+
const markY = fullChartBottom - radius - 2;
|
|
7728
|
+
for (const mark of timescaleMarks) {
|
|
7729
|
+
const ms = typeof mark.time === "number" ? mark.time : Date.parse(String(mark.time));
|
|
7730
|
+
if (!Number.isFinite(ms)) continue;
|
|
7731
|
+
const index = findNearestIndexForTimeMs(ms);
|
|
7732
|
+
if (index === null) continue;
|
|
7733
|
+
const x = chartLeft + (index + 0.5 - xStart) / xSpan * chartWidth;
|
|
7734
|
+
if (x < chartLeft - 10 || x > chartRight + 10) continue;
|
|
7735
|
+
const key = mark.id ?? `ts:${index}:${mark.label ?? ""}`;
|
|
7736
|
+
const hovered = hoveredMarkKey === `timescale|${key}`;
|
|
7737
|
+
drawMarkBadge(
|
|
7738
|
+
x,
|
|
7739
|
+
markY,
|
|
7740
|
+
hovered ? radius + 1.5 : radius,
|
|
7741
|
+
mark.color ?? "#5b8def",
|
|
7742
|
+
mark.shape ?? "circle",
|
|
7743
|
+
mark.label ?? "",
|
|
7744
|
+
mark.textColor ?? "#0b1220"
|
|
7745
|
+
);
|
|
7746
|
+
markRegions.push({ kind: "timescale", mark, id: key, x, y: markY, radius: radius + 3 });
|
|
7747
|
+
}
|
|
7748
|
+
}
|
|
7104
7749
|
if (labels.visible && labels.showCountdownToBarClose && lastPoint) {
|
|
7105
7750
|
const stepMs = getTimeStepMs();
|
|
7106
7751
|
const nowMs = Date.now() + (Number(mergedOptions.clockOffsetMs) || 0);
|
|
@@ -7726,6 +8371,22 @@ function createChart(element, options = {}) {
|
|
|
7726
8371
|
(region) => x >= region.x && x <= region.x + region.width && y >= region.y && y <= region.y + region.height
|
|
7727
8372
|
);
|
|
7728
8373
|
};
|
|
8374
|
+
const getAlertRegionAt = (x, y) => {
|
|
8375
|
+
const hits = alertRegions.filter(
|
|
8376
|
+
(region) => x >= region.x && x <= region.x + region.width && y >= region.y && y <= region.y + region.height
|
|
8377
|
+
);
|
|
8378
|
+
if (hits.length === 0) return null;
|
|
8379
|
+
return hits.find((region) => region.part === "remove") ?? hits[0];
|
|
8380
|
+
};
|
|
8381
|
+
const getMarkAt = (x, y) => {
|
|
8382
|
+
const tolerance = touchInputActive ? Math.max(1, mergedOptions.touch?.hitToleranceScale ?? 2.2) : 1;
|
|
8383
|
+
for (let index = markRegions.length - 1; index >= 0; index -= 1) {
|
|
8384
|
+
const region = markRegions[index];
|
|
8385
|
+
const reach = region.radius * tolerance;
|
|
8386
|
+
if (Math.abs(x - region.x) <= reach && Math.abs(y - region.y) <= reach) return region;
|
|
8387
|
+
}
|
|
8388
|
+
return null;
|
|
8389
|
+
};
|
|
7729
8390
|
const getCrosshairPriceActionRegion = (x, y) => {
|
|
7730
8391
|
if (!crosshairPriceActionRegion) {
|
|
7731
8392
|
return null;
|
|
@@ -8885,7 +9546,10 @@ function createChart(element, options = {}) {
|
|
|
8885
9546
|
pointerDownInfo = null;
|
|
8886
9547
|
orderDragState = null;
|
|
8887
9548
|
actionDragState = null;
|
|
8888
|
-
drawingDragState
|
|
9549
|
+
if (drawingDragState) {
|
|
9550
|
+
drawingDragState = null;
|
|
9551
|
+
commitHistory();
|
|
9552
|
+
}
|
|
8889
9553
|
canvas.style.cursor = "default";
|
|
8890
9554
|
setCrosshairPoint(null);
|
|
8891
9555
|
};
|
|
@@ -8980,6 +9644,20 @@ function createChart(element, options = {}) {
|
|
|
8980
9644
|
});
|
|
8981
9645
|
return;
|
|
8982
9646
|
}
|
|
9647
|
+
const markHit = getMarkAt(point.x, point.y);
|
|
9648
|
+
if (markHit && markClickHandler) {
|
|
9649
|
+
const rect = canvas.getBoundingClientRect();
|
|
9650
|
+
setCrosshairPoint(null);
|
|
9651
|
+
markClickHandler({
|
|
9652
|
+
kind: markHit.kind,
|
|
9653
|
+
mark: markHit.mark,
|
|
9654
|
+
x: markHit.x,
|
|
9655
|
+
y: markHit.y,
|
|
9656
|
+
clientX: rect.left + markHit.x,
|
|
9657
|
+
clientY: rect.top + markHit.y
|
|
9658
|
+
});
|
|
9659
|
+
return;
|
|
9660
|
+
}
|
|
8983
9661
|
const orderRegion = getOrderActionRegion(point.x, point.y);
|
|
8984
9662
|
if (orderRegion) {
|
|
8985
9663
|
if (orderRegion.draggable) {
|
|
@@ -9018,6 +9696,28 @@ function createChart(element, options = {}) {
|
|
|
9018
9696
|
setCrosshairPoint(null);
|
|
9019
9697
|
return;
|
|
9020
9698
|
}
|
|
9699
|
+
const alertHit = getAlertRegionAt(point.x, point.y);
|
|
9700
|
+
if (alertHit) {
|
|
9701
|
+
const target = alerts.find((alert) => alert.id === alertHit.id);
|
|
9702
|
+
if (target) {
|
|
9703
|
+
setCrosshairPoint(null);
|
|
9704
|
+
if (alertHit.part === "remove") {
|
|
9705
|
+
const removed = serializeAlert(target);
|
|
9706
|
+
removeAlert(target.id);
|
|
9707
|
+
alertActionHandler?.({ alert: removed, action: "remove" });
|
|
9708
|
+
return;
|
|
9709
|
+
}
|
|
9710
|
+
if (target.draggable) {
|
|
9711
|
+
activePointerId = event.pointerId;
|
|
9712
|
+
alertDragState = { id: target.id, startPrice: target.price, lastPrice: target.price, moved: false };
|
|
9713
|
+
capturePointer(event.pointerId);
|
|
9714
|
+
canvas.style.cursor = "ns-resize";
|
|
9715
|
+
return;
|
|
9716
|
+
}
|
|
9717
|
+
alertActionHandler?.({ alert: serializeAlert(target), action: "click", price: target.price });
|
|
9718
|
+
return;
|
|
9719
|
+
}
|
|
9720
|
+
}
|
|
9021
9721
|
const paneButtonHit = getPaneButtonHit(point.x, point.y);
|
|
9022
9722
|
if (paneButtonHit) {
|
|
9023
9723
|
setCrosshairPoint(null);
|
|
@@ -9260,6 +9960,24 @@ function createChart(element, options = {}) {
|
|
|
9260
9960
|
setCrosshairPoint(null);
|
|
9261
9961
|
return;
|
|
9262
9962
|
}
|
|
9963
|
+
if (alertDragState) {
|
|
9964
|
+
if (activePointerId !== null && event.pointerId !== activePointerId) {
|
|
9965
|
+
return;
|
|
9966
|
+
}
|
|
9967
|
+
const nextPrice = roundToPricePrecision(priceFromCanvasY(point.y));
|
|
9968
|
+
if (nextPrice !== alertDragState.lastPrice) {
|
|
9969
|
+
alertDragState.lastPrice = nextPrice;
|
|
9970
|
+
alertDragState.moved = true;
|
|
9971
|
+
const target = alerts.find((alert) => alert.id === alertDragState?.id);
|
|
9972
|
+
if (target) {
|
|
9973
|
+
target.price = nextPrice;
|
|
9974
|
+
target.lastPrice = data[data.length - 1]?.c ?? null;
|
|
9975
|
+
alertActionHandler?.({ alert: serializeAlert(target), action: "move", price: nextPrice, dragging: true });
|
|
9976
|
+
}
|
|
9977
|
+
scheduleDraw();
|
|
9978
|
+
}
|
|
9979
|
+
return;
|
|
9980
|
+
}
|
|
9263
9981
|
if (orderDragState) {
|
|
9264
9982
|
if (activePointerId !== null && event.pointerId !== activePointerId) {
|
|
9265
9983
|
return;
|
|
@@ -9326,12 +10044,49 @@ function createChart(element, options = {}) {
|
|
|
9326
10044
|
setCrosshairPoint(null);
|
|
9327
10045
|
return;
|
|
9328
10046
|
}
|
|
10047
|
+
const markHover = getMarkAt(point.x, point.y);
|
|
10048
|
+
const nextMarkKey = markHover ? `${markHover.kind}|${markHover.id}` : null;
|
|
10049
|
+
if (nextMarkKey !== hoveredMarkKey) {
|
|
10050
|
+
hoveredMarkKey = nextMarkKey;
|
|
10051
|
+
if (markHover) {
|
|
10052
|
+
const rect = canvas.getBoundingClientRect();
|
|
10053
|
+
markHoverHandler?.({
|
|
10054
|
+
kind: markHover.kind,
|
|
10055
|
+
mark: markHover.mark,
|
|
10056
|
+
x: markHover.x,
|
|
10057
|
+
y: markHover.y,
|
|
10058
|
+
clientX: rect.left + markHover.x,
|
|
10059
|
+
clientY: rect.top + markHover.y
|
|
10060
|
+
});
|
|
10061
|
+
} else {
|
|
10062
|
+
markHoverHandler?.(null);
|
|
10063
|
+
}
|
|
10064
|
+
scheduleDraw();
|
|
10065
|
+
}
|
|
10066
|
+
if (markHover) {
|
|
10067
|
+
canvas.title = markHover.mark.text ?? markHover.mark.label ?? "";
|
|
10068
|
+
canvas.style.cursor = "pointer";
|
|
10069
|
+
setCrosshairPoint(null);
|
|
10070
|
+
return;
|
|
10071
|
+
}
|
|
9329
10072
|
const orderDragRegion = getOrderDragRegion(point.x, point.y);
|
|
9330
10073
|
if (orderDragRegion) {
|
|
9331
10074
|
canvas.style.cursor = "ns-resize";
|
|
9332
10075
|
setCrosshairPoint(null);
|
|
9333
10076
|
return;
|
|
9334
10077
|
}
|
|
10078
|
+
const alertHover = getAlertRegionAt(point.x, point.y);
|
|
10079
|
+
const nextAlertId = alertHover?.id ?? null;
|
|
10080
|
+
if (nextAlertId !== hoveredAlertId) {
|
|
10081
|
+
hoveredAlertId = nextAlertId;
|
|
10082
|
+
scheduleDraw();
|
|
10083
|
+
}
|
|
10084
|
+
if (alertHover) {
|
|
10085
|
+
canvas.title = alertHover.part === "remove" ? "Remove alert" : "Drag to move alert";
|
|
10086
|
+
canvas.style.cursor = alertHover.part === "remove" ? "pointer" : "ns-resize";
|
|
10087
|
+
setCrosshairPoint(null);
|
|
10088
|
+
return;
|
|
10089
|
+
}
|
|
9335
10090
|
const hoveredPane = getPaneAt(point.x, point.y);
|
|
9336
10091
|
const nextHoveredPaneId = hoveredPane?.id ?? null;
|
|
9337
10092
|
if (nextHoveredPaneId !== hoveredPaneId) {
|
|
@@ -9489,6 +10244,21 @@ function createChart(element, options = {}) {
|
|
|
9489
10244
|
scheduleDraw();
|
|
9490
10245
|
return;
|
|
9491
10246
|
}
|
|
10247
|
+
if (alertDragState) {
|
|
10248
|
+
const target = alerts.find((alert) => alert.id === alertDragState?.id);
|
|
10249
|
+
if (target && alertDragState.moved) {
|
|
10250
|
+
alertActionHandler?.({
|
|
10251
|
+
alert: serializeAlert(target),
|
|
10252
|
+
action: "move",
|
|
10253
|
+
price: alertDragState.lastPrice,
|
|
10254
|
+
dragging: false
|
|
10255
|
+
});
|
|
10256
|
+
} else if (target) {
|
|
10257
|
+
alertActionHandler?.({ alert: serializeAlert(target), action: "click", price: target.price });
|
|
10258
|
+
}
|
|
10259
|
+
alertDragState = null;
|
|
10260
|
+
canvas.style.cursor = "default";
|
|
10261
|
+
}
|
|
9492
10262
|
if (orderDragState) {
|
|
9493
10263
|
const moved = orderDragState.lastPrice !== orderDragState.startPrice;
|
|
9494
10264
|
const finalLine = orderLines.find((line) => line.id === orderDragState?.orderId);
|
|
@@ -9535,9 +10305,14 @@ function createChart(element, options = {}) {
|
|
|
9535
10305
|
});
|
|
9536
10306
|
paneDividerDrag = null;
|
|
9537
10307
|
}
|
|
9538
|
-
if (event?.type === "pointerleave" && (hoveredPaneId !== null || hoveredPaneButton !== null)) {
|
|
10308
|
+
if (event?.type === "pointerleave" && (hoveredPaneId !== null || hoveredPaneButton !== null || hoveredAlertId !== null || hoveredMarkKey !== null)) {
|
|
9539
10309
|
hoveredPaneId = null;
|
|
9540
10310
|
hoveredPaneButton = null;
|
|
10311
|
+
hoveredAlertId = null;
|
|
10312
|
+
if (hoveredMarkKey !== null) {
|
|
10313
|
+
hoveredMarkKey = null;
|
|
10314
|
+
markHoverHandler?.(null);
|
|
10315
|
+
}
|
|
9541
10316
|
if (canvas.title) {
|
|
9542
10317
|
canvas.title = "";
|
|
9543
10318
|
}
|
|
@@ -9713,9 +10488,6 @@ function createChart(element, options = {}) {
|
|
|
9713
10488
|
if (keyboard.enabled === false) {
|
|
9714
10489
|
return;
|
|
9715
10490
|
}
|
|
9716
|
-
if (event.altKey || event.ctrlKey || event.metaKey) {
|
|
9717
|
-
return;
|
|
9718
|
-
}
|
|
9719
10491
|
const panStep = Math.max(1, keyboard.panBars ?? 1) * (event.shiftKey ? 10 : 1);
|
|
9720
10492
|
const emit = (action, drawing) => {
|
|
9721
10493
|
keyboardShortcutHandler?.({
|
|
@@ -9725,6 +10497,37 @@ function createChart(element, options = {}) {
|
|
|
9725
10497
|
...drawing ? { drawing } : {}
|
|
9726
10498
|
});
|
|
9727
10499
|
};
|
|
10500
|
+
if ((event.ctrlKey || event.metaKey) && !event.altKey) {
|
|
10501
|
+
const key = event.key.toLowerCase();
|
|
10502
|
+
if (keyboard.undoRedo !== false && (key === "z" || key === "y")) {
|
|
10503
|
+
const wantsRedo = key === "y" || event.shiftKey;
|
|
10504
|
+
const applied = wantsRedo ? redo() : undo();
|
|
10505
|
+
if (applied) {
|
|
10506
|
+
event.preventDefault();
|
|
10507
|
+
emit(wantsRedo ? "redo" : "undo");
|
|
10508
|
+
}
|
|
10509
|
+
return;
|
|
10510
|
+
}
|
|
10511
|
+
}
|
|
10512
|
+
if (event.altKey || event.ctrlKey || event.metaKey) {
|
|
10513
|
+
return;
|
|
10514
|
+
}
|
|
10515
|
+
if (replay && keyboard.replay !== false) {
|
|
10516
|
+
if (event.key === "ArrowRight" || event.key === "ArrowLeft") {
|
|
10517
|
+
const direction = event.key === "ArrowRight" ? 1 : -1;
|
|
10518
|
+
replayStep(direction * (event.shiftKey ? 10 : 1));
|
|
10519
|
+
emit(direction > 0 ? "replay-step-forward" : "replay-step-back");
|
|
10520
|
+
event.preventDefault();
|
|
10521
|
+
return;
|
|
10522
|
+
}
|
|
10523
|
+
if (event.key === " " || event.key === "Spacebar") {
|
|
10524
|
+
if (replay.playing) replayPause();
|
|
10525
|
+
else replayPlay();
|
|
10526
|
+
emit("replay-toggle-play");
|
|
10527
|
+
event.preventDefault();
|
|
10528
|
+
return;
|
|
10529
|
+
}
|
|
10530
|
+
}
|
|
9728
10531
|
switch (event.key) {
|
|
9729
10532
|
case "Delete":
|
|
9730
10533
|
case "Backspace": {
|
|
@@ -9870,6 +10673,20 @@ function createChart(element, options = {}) {
|
|
|
9870
10673
|
doubleClickEnabled = mergedOptions.doubleClickEnabled;
|
|
9871
10674
|
doubleClickAction = mergedOptions.doubleClickAction;
|
|
9872
10675
|
setIndicatorLiveThrottleMs(mergedOptions.indicatorUpdate?.liveThrottleMs ?? 80);
|
|
10676
|
+
if (nextOptions.timezone !== void 0) {
|
|
10677
|
+
timezoneSetting = nextOptions.timezone || "local";
|
|
10678
|
+
zoneClock = createZoneClock(timezoneSetting);
|
|
10679
|
+
}
|
|
10680
|
+
if (nextOptions.timeFormat !== void 0) {
|
|
10681
|
+
timeFormat = nextOptions.timeFormat === "12h" ? "12h" : "24h";
|
|
10682
|
+
}
|
|
10683
|
+
if (nextOptions.session !== void 0) {
|
|
10684
|
+
sessionOptions = { ...sessionOptions, ...nextOptions.session ?? {} };
|
|
10685
|
+
compiledSession = compileSession(sessionOptions.spec);
|
|
10686
|
+
}
|
|
10687
|
+
if (nextOptions.alerts !== void 0) {
|
|
10688
|
+
setAlerts(nextOptions.alerts ?? []);
|
|
10689
|
+
}
|
|
9873
10690
|
applyAccessibilityAttributes();
|
|
9874
10691
|
const isTickerSmoothingEnabled = mergedOptions.tickerLine?.smoothing ?? false;
|
|
9875
10692
|
if (!isTickerSmoothingEnabled) {
|
|
@@ -9930,7 +10747,7 @@ function createChart(element, options = {}) {
|
|
|
9930
10747
|
resetRightMarginCache();
|
|
9931
10748
|
scheduleDraw();
|
|
9932
10749
|
};
|
|
9933
|
-
const
|
|
10750
|
+
const setDataInternal = (nextData) => {
|
|
9934
10751
|
const hadData = data.length > 0;
|
|
9935
10752
|
const previousCount = data.length;
|
|
9936
10753
|
const previousCenterRounded = hadData ? Math.round(xCenter) : 0;
|
|
@@ -9972,9 +10789,10 @@ function createChart(element, options = {}) {
|
|
|
9972
10789
|
if (lastPoint) {
|
|
9973
10790
|
pushSmoothedTicker(lastPoint.c, lastPoint.v);
|
|
9974
10791
|
}
|
|
10792
|
+
evaluateAlertsFromLatestBar();
|
|
9975
10793
|
scheduleDraw();
|
|
9976
10794
|
};
|
|
9977
|
-
const
|
|
10795
|
+
const upsertBarInternal = (point) => {
|
|
9978
10796
|
const time = new Date(point.t);
|
|
9979
10797
|
const timeMs = time.getTime();
|
|
9980
10798
|
const open = Number(point.o);
|
|
@@ -9995,7 +10813,7 @@ function createChart(element, options = {}) {
|
|
|
9995
10813
|
};
|
|
9996
10814
|
const last = data[data.length - 1];
|
|
9997
10815
|
if (!last) {
|
|
9998
|
-
|
|
10816
|
+
setDataInternal([point]);
|
|
9999
10817
|
return;
|
|
10000
10818
|
}
|
|
10001
10819
|
const lastMs = last.time.getTime();
|
|
@@ -10017,8 +10835,167 @@ function createChart(element, options = {}) {
|
|
|
10017
10835
|
reanchorDrawingsToData();
|
|
10018
10836
|
}
|
|
10019
10837
|
pushSmoothedTicker(parsed.c, parsed.v);
|
|
10838
|
+
evaluateAlerts(parsed.c, timeMs, data.length - 1);
|
|
10020
10839
|
scheduleDraw();
|
|
10021
10840
|
};
|
|
10841
|
+
const toRawPoint = (point) => ({
|
|
10842
|
+
t: point.time.toISOString(),
|
|
10843
|
+
o: point.o,
|
|
10844
|
+
h: point.h,
|
|
10845
|
+
l: point.l,
|
|
10846
|
+
c: point.c,
|
|
10847
|
+
...point.v === void 0 ? {} : { v: point.v }
|
|
10848
|
+
});
|
|
10849
|
+
const getReplayState = () => {
|
|
10850
|
+
if (!replay) {
|
|
10851
|
+
return {
|
|
10852
|
+
active: false,
|
|
10853
|
+
playing: false,
|
|
10854
|
+
speed: 1,
|
|
10855
|
+
index: Math.max(0, data.length - 1),
|
|
10856
|
+
total: data.length,
|
|
10857
|
+
atEnd: true,
|
|
10858
|
+
timeMs: data.length > 0 ? data[data.length - 1].time.getTime() : null
|
|
10859
|
+
};
|
|
10860
|
+
}
|
|
10861
|
+
const total = replay.fullRaw.length;
|
|
10862
|
+
const revealed = replay.fullRaw[replay.cursor];
|
|
10863
|
+
const revealedMs = revealed ? Date.parse(String(revealed.t)) : Number.NaN;
|
|
10864
|
+
return {
|
|
10865
|
+
active: true,
|
|
10866
|
+
playing: replay.playing,
|
|
10867
|
+
speed: replay.speed,
|
|
10868
|
+
index: replay.cursor,
|
|
10869
|
+
total,
|
|
10870
|
+
atEnd: replay.cursor >= total - 1,
|
|
10871
|
+
timeMs: Number.isFinite(revealedMs) ? revealedMs : null
|
|
10872
|
+
};
|
|
10873
|
+
};
|
|
10874
|
+
const emitReplayState = () => {
|
|
10875
|
+
replayStateHandler?.(getReplayState());
|
|
10876
|
+
};
|
|
10877
|
+
const applyReplaySlice = () => {
|
|
10878
|
+
if (!replay) return;
|
|
10879
|
+
setDataInternal(replay.fullRaw.slice(0, replay.cursor + 1));
|
|
10880
|
+
};
|
|
10881
|
+
const clearReplayTimer = () => {
|
|
10882
|
+
if (replay?.timerId) {
|
|
10883
|
+
clearTimeout(replay.timerId);
|
|
10884
|
+
replay.timerId = null;
|
|
10885
|
+
}
|
|
10886
|
+
};
|
|
10887
|
+
const scheduleReplayTick = () => {
|
|
10888
|
+
if (!replay || !replay.playing) return;
|
|
10889
|
+
clearReplayTimer();
|
|
10890
|
+
const intervalMs = Math.max(16, 1e3 / Math.max(0.05, replay.speed));
|
|
10891
|
+
replay.timerId = setTimeout(() => {
|
|
10892
|
+
if (!replay || !replay.playing) return;
|
|
10893
|
+
if (replay.cursor >= replay.fullRaw.length - 1) {
|
|
10894
|
+
replay.playing = false;
|
|
10895
|
+
clearReplayTimer();
|
|
10896
|
+
emitReplayState();
|
|
10897
|
+
return;
|
|
10898
|
+
}
|
|
10899
|
+
replay.cursor += 1;
|
|
10900
|
+
applyReplaySlice();
|
|
10901
|
+
emitReplayState();
|
|
10902
|
+
scheduleReplayTick();
|
|
10903
|
+
}, intervalMs);
|
|
10904
|
+
};
|
|
10905
|
+
const startReplay = (startOptions = {}) => {
|
|
10906
|
+
const source = replay ? replay.fullRaw : data.map((point) => toRawPoint(point));
|
|
10907
|
+
if (source.length === 0) return;
|
|
10908
|
+
clearReplayTimer();
|
|
10909
|
+
let cursor;
|
|
10910
|
+
if (Number.isFinite(startOptions.fromTimeMs)) {
|
|
10911
|
+
const target = Number(startOptions.fromTimeMs);
|
|
10912
|
+
const resolved = findNearestIndexForTimeMs(target);
|
|
10913
|
+
cursor = resolved ?? Math.floor(source.length * 0.7);
|
|
10914
|
+
} else if (Number.isFinite(startOptions.fromIndex)) {
|
|
10915
|
+
cursor = Math.floor(Number(startOptions.fromIndex));
|
|
10916
|
+
} else {
|
|
10917
|
+
cursor = Math.floor(source.length * 0.7);
|
|
10918
|
+
}
|
|
10919
|
+
cursor = clamp(cursor, 0, source.length - 1);
|
|
10920
|
+
const speed = Math.max(0.05, Number(startOptions.speed) || replay?.speed || 1);
|
|
10921
|
+
replay = { fullRaw: source, cursor, playing: false, speed, timerId: null };
|
|
10922
|
+
applyReplaySlice();
|
|
10923
|
+
setFollowingLatest(true);
|
|
10924
|
+
if (startOptions.play) {
|
|
10925
|
+
replay.playing = true;
|
|
10926
|
+
scheduleReplayTick();
|
|
10927
|
+
}
|
|
10928
|
+
emitReplayState();
|
|
10929
|
+
};
|
|
10930
|
+
const stopReplay = () => {
|
|
10931
|
+
if (!replay) return;
|
|
10932
|
+
clearReplayTimer();
|
|
10933
|
+
const full = replay.fullRaw;
|
|
10934
|
+
replay = null;
|
|
10935
|
+
setDataInternal(full);
|
|
10936
|
+
setFollowingLatest(true);
|
|
10937
|
+
emitReplayState();
|
|
10938
|
+
};
|
|
10939
|
+
const replayStep = (bars = 1) => {
|
|
10940
|
+
if (!replay) return;
|
|
10941
|
+
const step = Math.trunc(bars) || 1;
|
|
10942
|
+
replaySeek(replay.cursor + step);
|
|
10943
|
+
};
|
|
10944
|
+
const replaySeek = (index) => {
|
|
10945
|
+
if (!replay || !Number.isFinite(index)) return;
|
|
10946
|
+
const next = clamp(Math.round(index), 0, replay.fullRaw.length - 1);
|
|
10947
|
+
if (next === replay.cursor) return;
|
|
10948
|
+
replay.cursor = next;
|
|
10949
|
+
applyReplaySlice();
|
|
10950
|
+
emitReplayState();
|
|
10951
|
+
};
|
|
10952
|
+
const replayPlay = (speed) => {
|
|
10953
|
+
if (!replay) return;
|
|
10954
|
+
if (Number.isFinite(speed)) replay.speed = Math.max(0.05, Number(speed));
|
|
10955
|
+
if (replay.cursor >= replay.fullRaw.length - 1) return;
|
|
10956
|
+
replay.playing = true;
|
|
10957
|
+
scheduleReplayTick();
|
|
10958
|
+
emitReplayState();
|
|
10959
|
+
};
|
|
10960
|
+
const replayPause = () => {
|
|
10961
|
+
if (!replay || !replay.playing) return;
|
|
10962
|
+
replay.playing = false;
|
|
10963
|
+
clearReplayTimer();
|
|
10964
|
+
emitReplayState();
|
|
10965
|
+
};
|
|
10966
|
+
const setReplaySpeed = (speed) => {
|
|
10967
|
+
if (!replay || !Number.isFinite(speed)) return;
|
|
10968
|
+
replay.speed = clamp(Number(speed), 0.05, 100);
|
|
10969
|
+
if (replay.playing) scheduleReplayTick();
|
|
10970
|
+
emitReplayState();
|
|
10971
|
+
};
|
|
10972
|
+
const onReplayStateChange = (handler) => {
|
|
10973
|
+
replayStateHandler = handler;
|
|
10974
|
+
};
|
|
10975
|
+
const setData = (nextData) => {
|
|
10976
|
+
if (replay) {
|
|
10977
|
+
replay.fullRaw = nextData.slice();
|
|
10978
|
+
replay.cursor = clamp(replay.cursor, 0, Math.max(0, replay.fullRaw.length - 1));
|
|
10979
|
+
applyReplaySlice();
|
|
10980
|
+
emitReplayState();
|
|
10981
|
+
return;
|
|
10982
|
+
}
|
|
10983
|
+
setDataInternal(nextData);
|
|
10984
|
+
};
|
|
10985
|
+
const upsertBar = (point) => {
|
|
10986
|
+
if (replay) {
|
|
10987
|
+
const timeMs = Date.parse(String(point.t));
|
|
10988
|
+
if (!Number.isFinite(timeMs)) return;
|
|
10989
|
+
const buffer = replay.fullRaw;
|
|
10990
|
+
const lastRaw = buffer[buffer.length - 1];
|
|
10991
|
+
const lastMs = lastRaw ? Date.parse(String(lastRaw.t)) : Number.NaN;
|
|
10992
|
+
if (Number.isFinite(lastMs) && timeMs === lastMs) buffer[buffer.length - 1] = point;
|
|
10993
|
+
else if (!Number.isFinite(lastMs) || timeMs > lastMs) buffer.push(point);
|
|
10994
|
+
emitReplayState();
|
|
10995
|
+
return;
|
|
10996
|
+
}
|
|
10997
|
+
upsertBarInternal(point);
|
|
10998
|
+
};
|
|
10022
10999
|
const setPriceLines = (lines) => {
|
|
10023
11000
|
priceLines = lines.map((line, index) => ({
|
|
10024
11001
|
...line,
|
|
@@ -10207,6 +11184,96 @@ function createChart(element, options = {}) {
|
|
|
10207
11184
|
tradeMarkers = Array.isArray(markers) ? markers.slice() : [];
|
|
10208
11185
|
scheduleDraw();
|
|
10209
11186
|
};
|
|
11187
|
+
const setBarMarks = (marks) => {
|
|
11188
|
+
barMarks = Array.isArray(marks) ? marks.slice() : [];
|
|
11189
|
+
scheduleDraw();
|
|
11190
|
+
};
|
|
11191
|
+
const getBarMarks = () => barMarks.slice();
|
|
11192
|
+
const setTimescaleMarks = (marks) => {
|
|
11193
|
+
timescaleMarks = Array.isArray(marks) ? marks.slice() : [];
|
|
11194
|
+
scheduleDraw();
|
|
11195
|
+
};
|
|
11196
|
+
const getTimescaleMarks = () => timescaleMarks.slice();
|
|
11197
|
+
const onMarkClick = (handler) => {
|
|
11198
|
+
markClickHandler = handler;
|
|
11199
|
+
};
|
|
11200
|
+
const onMarkHover = (handler) => {
|
|
11201
|
+
markHoverHandler = handler;
|
|
11202
|
+
};
|
|
11203
|
+
const setAlerts = (nextAlerts) => {
|
|
11204
|
+
alerts = (Array.isArray(nextAlerts) ? nextAlerts : []).map((alert) => normalizeAlert(alert));
|
|
11205
|
+
const last = data[data.length - 1];
|
|
11206
|
+
if (last) {
|
|
11207
|
+
for (const alert of alerts) alert.lastPrice = last.c;
|
|
11208
|
+
}
|
|
11209
|
+
resetRightMarginCache();
|
|
11210
|
+
scheduleDraw();
|
|
11211
|
+
};
|
|
11212
|
+
const addAlert = (alert) => {
|
|
11213
|
+
const next = normalizeAlert(alert);
|
|
11214
|
+
const last = data[data.length - 1];
|
|
11215
|
+
if (last) next.lastPrice = last.c;
|
|
11216
|
+
alerts.push(next);
|
|
11217
|
+
resetRightMarginCache();
|
|
11218
|
+
scheduleDraw();
|
|
11219
|
+
return next.id;
|
|
11220
|
+
};
|
|
11221
|
+
const updateAlert = (id, patch) => {
|
|
11222
|
+
const target = alerts.find((alert) => alert.id === id);
|
|
11223
|
+
if (!target) return;
|
|
11224
|
+
if (Number.isFinite(patch.price)) target.price = Number(patch.price);
|
|
11225
|
+
if (patch.condition) target.condition = patch.condition;
|
|
11226
|
+
if (patch.label !== void 0) target.label = patch.label;
|
|
11227
|
+
if (patch.color !== void 0) target.color = patch.color;
|
|
11228
|
+
if (patch.active !== void 0) target.active = patch.active;
|
|
11229
|
+
if (patch.once !== void 0) target.once = patch.once;
|
|
11230
|
+
if (patch.draggable !== void 0) target.draggable = patch.draggable;
|
|
11231
|
+
if (patch.triggered !== void 0) {
|
|
11232
|
+
target.triggered = patch.triggered;
|
|
11233
|
+
if (!patch.triggered) target.lastPrice = data[data.length - 1]?.c ?? null;
|
|
11234
|
+
}
|
|
11235
|
+
resetRightMarginCache();
|
|
11236
|
+
scheduleDraw();
|
|
11237
|
+
};
|
|
11238
|
+
const removeAlert = (id) => {
|
|
11239
|
+
alerts = alerts.filter((alert) => alert.id !== id);
|
|
11240
|
+
if (hoveredAlertId === id) hoveredAlertId = null;
|
|
11241
|
+
resetRightMarginCache();
|
|
11242
|
+
scheduleDraw();
|
|
11243
|
+
};
|
|
11244
|
+
const getAlerts = () => alerts.map((alert) => serializeAlert(alert));
|
|
11245
|
+
const onAlertTrigger = (handler) => {
|
|
11246
|
+
alertTriggerHandler = handler;
|
|
11247
|
+
};
|
|
11248
|
+
const onAlertAction = (handler) => {
|
|
11249
|
+
alertActionHandler = handler;
|
|
11250
|
+
};
|
|
11251
|
+
const setTimezone = (timezone) => {
|
|
11252
|
+
timezoneSetting = timezone || "local";
|
|
11253
|
+
zoneClock = createZoneClock(timezoneSetting);
|
|
11254
|
+
scheduleDraw();
|
|
11255
|
+
};
|
|
11256
|
+
const getTimezone = () => timezoneSetting;
|
|
11257
|
+
const setTimeFormat = (format) => {
|
|
11258
|
+
timeFormat = format === "12h" ? "12h" : "24h";
|
|
11259
|
+
resetRightMarginCache();
|
|
11260
|
+
scheduleDraw();
|
|
11261
|
+
};
|
|
11262
|
+
const getTimeFormat = () => timeFormat;
|
|
11263
|
+
const setSession = (session) => {
|
|
11264
|
+
if (session === null) {
|
|
11265
|
+
sessionOptions = { ...sessionOptions, spec: null };
|
|
11266
|
+
} else if (typeof session === "string") {
|
|
11267
|
+
sessionOptions = { ...sessionOptions, spec: session };
|
|
11268
|
+
} else if ("segments" in session || "timezone" in session || "days" in session) {
|
|
11269
|
+
sessionOptions = { ...sessionOptions, spec: session };
|
|
11270
|
+
} else {
|
|
11271
|
+
sessionOptions = { ...sessionOptions, ...session };
|
|
11272
|
+
}
|
|
11273
|
+
compiledSession = compileSession(sessionOptions.spec);
|
|
11274
|
+
scheduleDraw();
|
|
11275
|
+
};
|
|
11276
|
+
const getSession = () => ({ ...sessionOptions });
|
|
10210
11277
|
const setMagnetMode = (mode) => {
|
|
10211
11278
|
magnetMode = mode;
|
|
10212
11279
|
};
|
|
@@ -10226,7 +11293,13 @@ function createChart(element, options = {}) {
|
|
|
10226
11293
|
drawings = dedupeDrawingIds(nextDrawings.map((drawing) => normalizeDrawingState(drawing)));
|
|
10227
11294
|
draftDrawing = null;
|
|
10228
11295
|
reanchorDrawingsToData();
|
|
10229
|
-
|
|
11296
|
+
historySuspended += 1;
|
|
11297
|
+
try {
|
|
11298
|
+
emitDrawingsChange();
|
|
11299
|
+
} finally {
|
|
11300
|
+
historySuspended -= 1;
|
|
11301
|
+
}
|
|
11302
|
+
resetHistoryBaseline();
|
|
10230
11303
|
scheduleDraw();
|
|
10231
11304
|
};
|
|
10232
11305
|
const addDrawing = (drawing) => {
|
|
@@ -10311,7 +11384,10 @@ function createChart(element, options = {}) {
|
|
|
10311
11384
|
indicators: getIndicators(),
|
|
10312
11385
|
magnetMode: getMagnetMode(),
|
|
10313
11386
|
priceScale: getPriceScale(),
|
|
10314
|
-
drawingDefaults
|
|
11387
|
+
drawingDefaults,
|
|
11388
|
+
alerts: getAlerts(),
|
|
11389
|
+
timezone: getTimezone(),
|
|
11390
|
+
timeFormat: getTimeFormat()
|
|
10315
11391
|
};
|
|
10316
11392
|
};
|
|
10317
11393
|
const loadState = (state) => {
|
|
@@ -10340,9 +11416,19 @@ function createChart(element, options = {}) {
|
|
|
10340
11416
|
}
|
|
10341
11417
|
}
|
|
10342
11418
|
}
|
|
11419
|
+
if (Array.isArray(state.alerts)) {
|
|
11420
|
+
setAlerts(state.alerts);
|
|
11421
|
+
}
|
|
11422
|
+
if (typeof state.timezone === "string") {
|
|
11423
|
+
setTimezone(state.timezone);
|
|
11424
|
+
}
|
|
11425
|
+
if (state.timeFormat === "12h" || state.timeFormat === "24h") {
|
|
11426
|
+
setTimeFormat(state.timeFormat);
|
|
11427
|
+
}
|
|
10343
11428
|
if (state.viewport && typeof state.viewport === "object") {
|
|
10344
11429
|
setViewport(state.viewport);
|
|
10345
11430
|
}
|
|
11431
|
+
clearHistory();
|
|
10346
11432
|
};
|
|
10347
11433
|
const takeScreenshot = (options2 = {}) => {
|
|
10348
11434
|
return canvas.toDataURL(options2.type ?? "image/png", options2.quality);
|
|
@@ -10350,6 +11436,8 @@ function createChart(element, options = {}) {
|
|
|
10350
11436
|
const destroy = () => {
|
|
10351
11437
|
cancelViewportTween();
|
|
10352
11438
|
cancelLongPressTimer();
|
|
11439
|
+
clearReplayTimer();
|
|
11440
|
+
replay = null;
|
|
10353
11441
|
datafeedUnsubscribe?.();
|
|
10354
11442
|
datafeedUnsubscribe = null;
|
|
10355
11443
|
datafeed = null;
|
|
@@ -10383,6 +11471,10 @@ function createChart(element, options = {}) {
|
|
|
10383
11471
|
}
|
|
10384
11472
|
element.innerHTML = "";
|
|
10385
11473
|
};
|
|
11474
|
+
if (Array.isArray(options.alerts) && options.alerts.length > 0) {
|
|
11475
|
+
setAlerts(options.alerts);
|
|
11476
|
+
}
|
|
11477
|
+
resetHistoryBaseline();
|
|
10386
11478
|
draw();
|
|
10387
11479
|
armCountdownHeartbeat();
|
|
10388
11480
|
return {
|
|
@@ -10466,6 +11558,43 @@ function createChart(element, options = {}) {
|
|
|
10466
11558
|
saveState,
|
|
10467
11559
|
loadState,
|
|
10468
11560
|
takeScreenshot,
|
|
11561
|
+
setTimezone,
|
|
11562
|
+
getTimezone,
|
|
11563
|
+
setTimeFormat,
|
|
11564
|
+
getTimeFormat,
|
|
11565
|
+
setSession,
|
|
11566
|
+
getSession,
|
|
11567
|
+
undo,
|
|
11568
|
+
redo,
|
|
11569
|
+
canUndo: () => historyPast.length > 0,
|
|
11570
|
+
canRedo: () => historyFuture.length > 0,
|
|
11571
|
+
getUndoRedoState,
|
|
11572
|
+
onUndoRedoStateChange: (handler) => {
|
|
11573
|
+
undoRedoStateHandler = handler;
|
|
11574
|
+
},
|
|
11575
|
+
clearHistory,
|
|
11576
|
+
startReplay,
|
|
11577
|
+
stopReplay,
|
|
11578
|
+
replayStep,
|
|
11579
|
+
replaySeek,
|
|
11580
|
+
replayPlay,
|
|
11581
|
+
replayPause,
|
|
11582
|
+
setReplaySpeed,
|
|
11583
|
+
getReplayState,
|
|
11584
|
+
onReplayStateChange,
|
|
11585
|
+
setAlerts,
|
|
11586
|
+
addAlert,
|
|
11587
|
+
updateAlert,
|
|
11588
|
+
removeAlert,
|
|
11589
|
+
getAlerts,
|
|
11590
|
+
onAlertTrigger,
|
|
11591
|
+
onAlertAction,
|
|
11592
|
+
setBarMarks,
|
|
11593
|
+
getBarMarks,
|
|
11594
|
+
setTimescaleMarks,
|
|
11595
|
+
getTimescaleMarks,
|
|
11596
|
+
onMarkClick,
|
|
11597
|
+
onMarkHover,
|
|
10469
11598
|
resize,
|
|
10470
11599
|
destroy
|
|
10471
11600
|
};
|
|
@@ -10476,8 +11605,10 @@ export {
|
|
|
10476
11605
|
FIB_DEFAULT_PALETTE,
|
|
10477
11606
|
POSITION_DEFAULT_COLORS,
|
|
10478
11607
|
SCRIPT_SOURCE_INPUT_VALUES,
|
|
11608
|
+
SESSION_PRESETS,
|
|
10479
11609
|
compileScriptIndicator,
|
|
10480
11610
|
createChart,
|
|
10481
11611
|
extractScriptInputs,
|
|
11612
|
+
isValidTimeZone,
|
|
10482
11613
|
validateScriptSource
|
|
10483
11614
|
};
|