hyperprop-charting-library 0.1.143 → 0.1.144
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 +1184 -72
- package/dist/hyperprop-charting-library.d.ts +268 -2
- package/dist/hyperprop-charting-library.js +1182 -72
- package/dist/index.cjs +1184 -72
- package/dist/index.d.cts +268 -2
- package/dist/index.d.ts +268 -2
- package/dist/index.js +1182 -72
- package/docs/API.md +171 -1
- package/package.json +3 -2
|
@@ -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,21 @@ 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
|
+
}
|
|
9728
10515
|
switch (event.key) {
|
|
9729
10516
|
case "Delete":
|
|
9730
10517
|
case "Backspace": {
|
|
@@ -9870,6 +10657,20 @@ function createChart(element, options = {}) {
|
|
|
9870
10657
|
doubleClickEnabled = mergedOptions.doubleClickEnabled;
|
|
9871
10658
|
doubleClickAction = mergedOptions.doubleClickAction;
|
|
9872
10659
|
setIndicatorLiveThrottleMs(mergedOptions.indicatorUpdate?.liveThrottleMs ?? 80);
|
|
10660
|
+
if (nextOptions.timezone !== void 0) {
|
|
10661
|
+
timezoneSetting = nextOptions.timezone || "local";
|
|
10662
|
+
zoneClock = createZoneClock(timezoneSetting);
|
|
10663
|
+
}
|
|
10664
|
+
if (nextOptions.timeFormat !== void 0) {
|
|
10665
|
+
timeFormat = nextOptions.timeFormat === "12h" ? "12h" : "24h";
|
|
10666
|
+
}
|
|
10667
|
+
if (nextOptions.session !== void 0) {
|
|
10668
|
+
sessionOptions = { ...sessionOptions, ...nextOptions.session ?? {} };
|
|
10669
|
+
compiledSession = compileSession(sessionOptions.spec);
|
|
10670
|
+
}
|
|
10671
|
+
if (nextOptions.alerts !== void 0) {
|
|
10672
|
+
setAlerts(nextOptions.alerts ?? []);
|
|
10673
|
+
}
|
|
9873
10674
|
applyAccessibilityAttributes();
|
|
9874
10675
|
const isTickerSmoothingEnabled = mergedOptions.tickerLine?.smoothing ?? false;
|
|
9875
10676
|
if (!isTickerSmoothingEnabled) {
|
|
@@ -9930,7 +10731,7 @@ function createChart(element, options = {}) {
|
|
|
9930
10731
|
resetRightMarginCache();
|
|
9931
10732
|
scheduleDraw();
|
|
9932
10733
|
};
|
|
9933
|
-
const
|
|
10734
|
+
const setDataInternal = (nextData) => {
|
|
9934
10735
|
const hadData = data.length > 0;
|
|
9935
10736
|
const previousCount = data.length;
|
|
9936
10737
|
const previousCenterRounded = hadData ? Math.round(xCenter) : 0;
|
|
@@ -9972,9 +10773,10 @@ function createChart(element, options = {}) {
|
|
|
9972
10773
|
if (lastPoint) {
|
|
9973
10774
|
pushSmoothedTicker(lastPoint.c, lastPoint.v);
|
|
9974
10775
|
}
|
|
10776
|
+
evaluateAlertsFromLatestBar();
|
|
9975
10777
|
scheduleDraw();
|
|
9976
10778
|
};
|
|
9977
|
-
const
|
|
10779
|
+
const upsertBarInternal = (point) => {
|
|
9978
10780
|
const time = new Date(point.t);
|
|
9979
10781
|
const timeMs = time.getTime();
|
|
9980
10782
|
const open = Number(point.o);
|
|
@@ -9995,7 +10797,7 @@ function createChart(element, options = {}) {
|
|
|
9995
10797
|
};
|
|
9996
10798
|
const last = data[data.length - 1];
|
|
9997
10799
|
if (!last) {
|
|
9998
|
-
|
|
10800
|
+
setDataInternal([point]);
|
|
9999
10801
|
return;
|
|
10000
10802
|
}
|
|
10001
10803
|
const lastMs = last.time.getTime();
|
|
@@ -10017,8 +10819,163 @@ function createChart(element, options = {}) {
|
|
|
10017
10819
|
reanchorDrawingsToData();
|
|
10018
10820
|
}
|
|
10019
10821
|
pushSmoothedTicker(parsed.c, parsed.v);
|
|
10822
|
+
evaluateAlerts(parsed.c, timeMs, data.length - 1);
|
|
10020
10823
|
scheduleDraw();
|
|
10021
10824
|
};
|
|
10825
|
+
const toRawPoint = (point) => ({
|
|
10826
|
+
t: point.time.toISOString(),
|
|
10827
|
+
o: point.o,
|
|
10828
|
+
h: point.h,
|
|
10829
|
+
l: point.l,
|
|
10830
|
+
c: point.c,
|
|
10831
|
+
...point.v === void 0 ? {} : { v: point.v }
|
|
10832
|
+
});
|
|
10833
|
+
const getReplayState = () => {
|
|
10834
|
+
if (!replay) {
|
|
10835
|
+
return {
|
|
10836
|
+
active: false,
|
|
10837
|
+
playing: false,
|
|
10838
|
+
speed: 1,
|
|
10839
|
+
index: Math.max(0, data.length - 1),
|
|
10840
|
+
total: data.length,
|
|
10841
|
+
atEnd: true,
|
|
10842
|
+
timeMs: data.length > 0 ? data[data.length - 1].time.getTime() : null
|
|
10843
|
+
};
|
|
10844
|
+
}
|
|
10845
|
+
const total = replay.fullRaw.length;
|
|
10846
|
+
const revealed = replay.fullRaw[replay.cursor];
|
|
10847
|
+
const revealedMs = revealed ? Date.parse(String(revealed.t)) : Number.NaN;
|
|
10848
|
+
return {
|
|
10849
|
+
active: true,
|
|
10850
|
+
playing: replay.playing,
|
|
10851
|
+
speed: replay.speed,
|
|
10852
|
+
index: replay.cursor,
|
|
10853
|
+
total,
|
|
10854
|
+
atEnd: replay.cursor >= total - 1,
|
|
10855
|
+
timeMs: Number.isFinite(revealedMs) ? revealedMs : null
|
|
10856
|
+
};
|
|
10857
|
+
};
|
|
10858
|
+
const emitReplayState = () => {
|
|
10859
|
+
replayStateHandler?.(getReplayState());
|
|
10860
|
+
};
|
|
10861
|
+
const applyReplaySlice = () => {
|
|
10862
|
+
if (!replay) return;
|
|
10863
|
+
setDataInternal(replay.fullRaw.slice(0, replay.cursor + 1));
|
|
10864
|
+
};
|
|
10865
|
+
const clearReplayTimer = () => {
|
|
10866
|
+
if (replay?.timerId) {
|
|
10867
|
+
clearTimeout(replay.timerId);
|
|
10868
|
+
replay.timerId = null;
|
|
10869
|
+
}
|
|
10870
|
+
};
|
|
10871
|
+
const scheduleReplayTick = () => {
|
|
10872
|
+
if (!replay || !replay.playing) return;
|
|
10873
|
+
clearReplayTimer();
|
|
10874
|
+
const intervalMs = Math.max(16, 1e3 / Math.max(0.05, replay.speed));
|
|
10875
|
+
replay.timerId = setTimeout(() => {
|
|
10876
|
+
if (!replay || !replay.playing) return;
|
|
10877
|
+
if (replay.cursor >= replay.fullRaw.length - 1) {
|
|
10878
|
+
replay.playing = false;
|
|
10879
|
+
clearReplayTimer();
|
|
10880
|
+
emitReplayState();
|
|
10881
|
+
return;
|
|
10882
|
+
}
|
|
10883
|
+
replay.cursor += 1;
|
|
10884
|
+
applyReplaySlice();
|
|
10885
|
+
emitReplayState();
|
|
10886
|
+
scheduleReplayTick();
|
|
10887
|
+
}, intervalMs);
|
|
10888
|
+
};
|
|
10889
|
+
const startReplay = (startOptions = {}) => {
|
|
10890
|
+
const source = replay ? replay.fullRaw : data.map((point) => toRawPoint(point));
|
|
10891
|
+
if (source.length === 0) return;
|
|
10892
|
+
clearReplayTimer();
|
|
10893
|
+
let cursor;
|
|
10894
|
+
if (Number.isFinite(startOptions.fromTimeMs)) {
|
|
10895
|
+
const target = Number(startOptions.fromTimeMs);
|
|
10896
|
+
const resolved = findNearestIndexForTimeMs(target);
|
|
10897
|
+
cursor = resolved ?? Math.floor(source.length * 0.7);
|
|
10898
|
+
} else if (Number.isFinite(startOptions.fromIndex)) {
|
|
10899
|
+
cursor = Math.floor(Number(startOptions.fromIndex));
|
|
10900
|
+
} else {
|
|
10901
|
+
cursor = Math.floor(source.length * 0.7);
|
|
10902
|
+
}
|
|
10903
|
+
cursor = clamp(cursor, 0, source.length - 1);
|
|
10904
|
+
const speed = Math.max(0.05, Number(startOptions.speed) || replay?.speed || 1);
|
|
10905
|
+
replay = { fullRaw: source, cursor, playing: false, speed, timerId: null };
|
|
10906
|
+
applyReplaySlice();
|
|
10907
|
+
setFollowingLatest(true);
|
|
10908
|
+
if (startOptions.play) {
|
|
10909
|
+
replay.playing = true;
|
|
10910
|
+
scheduleReplayTick();
|
|
10911
|
+
}
|
|
10912
|
+
emitReplayState();
|
|
10913
|
+
};
|
|
10914
|
+
const stopReplay = () => {
|
|
10915
|
+
if (!replay) return;
|
|
10916
|
+
clearReplayTimer();
|
|
10917
|
+
const full = replay.fullRaw;
|
|
10918
|
+
replay = null;
|
|
10919
|
+
setDataInternal(full);
|
|
10920
|
+
setFollowingLatest(true);
|
|
10921
|
+
emitReplayState();
|
|
10922
|
+
};
|
|
10923
|
+
const replayStep = (bars = 1) => {
|
|
10924
|
+
if (!replay) return;
|
|
10925
|
+
const step = Math.trunc(bars) || 1;
|
|
10926
|
+
const next = clamp(replay.cursor + step, 0, replay.fullRaw.length - 1);
|
|
10927
|
+
if (next === replay.cursor) return;
|
|
10928
|
+
replay.cursor = next;
|
|
10929
|
+
applyReplaySlice();
|
|
10930
|
+
emitReplayState();
|
|
10931
|
+
};
|
|
10932
|
+
const replayPlay = (speed) => {
|
|
10933
|
+
if (!replay) return;
|
|
10934
|
+
if (Number.isFinite(speed)) replay.speed = Math.max(0.05, Number(speed));
|
|
10935
|
+
if (replay.cursor >= replay.fullRaw.length - 1) return;
|
|
10936
|
+
replay.playing = true;
|
|
10937
|
+
scheduleReplayTick();
|
|
10938
|
+
emitReplayState();
|
|
10939
|
+
};
|
|
10940
|
+
const replayPause = () => {
|
|
10941
|
+
if (!replay || !replay.playing) return;
|
|
10942
|
+
replay.playing = false;
|
|
10943
|
+
clearReplayTimer();
|
|
10944
|
+
emitReplayState();
|
|
10945
|
+
};
|
|
10946
|
+
const setReplaySpeed = (speed) => {
|
|
10947
|
+
if (!replay || !Number.isFinite(speed)) return;
|
|
10948
|
+
replay.speed = clamp(Number(speed), 0.05, 100);
|
|
10949
|
+
if (replay.playing) scheduleReplayTick();
|
|
10950
|
+
emitReplayState();
|
|
10951
|
+
};
|
|
10952
|
+
const onReplayStateChange = (handler) => {
|
|
10953
|
+
replayStateHandler = handler;
|
|
10954
|
+
};
|
|
10955
|
+
const setData = (nextData) => {
|
|
10956
|
+
if (replay) {
|
|
10957
|
+
replay.fullRaw = nextData.slice();
|
|
10958
|
+
replay.cursor = clamp(replay.cursor, 0, Math.max(0, replay.fullRaw.length - 1));
|
|
10959
|
+
applyReplaySlice();
|
|
10960
|
+
emitReplayState();
|
|
10961
|
+
return;
|
|
10962
|
+
}
|
|
10963
|
+
setDataInternal(nextData);
|
|
10964
|
+
};
|
|
10965
|
+
const upsertBar = (point) => {
|
|
10966
|
+
if (replay) {
|
|
10967
|
+
const timeMs = Date.parse(String(point.t));
|
|
10968
|
+
if (!Number.isFinite(timeMs)) return;
|
|
10969
|
+
const buffer = replay.fullRaw;
|
|
10970
|
+
const lastRaw = buffer[buffer.length - 1];
|
|
10971
|
+
const lastMs = lastRaw ? Date.parse(String(lastRaw.t)) : Number.NaN;
|
|
10972
|
+
if (Number.isFinite(lastMs) && timeMs === lastMs) buffer[buffer.length - 1] = point;
|
|
10973
|
+
else if (!Number.isFinite(lastMs) || timeMs > lastMs) buffer.push(point);
|
|
10974
|
+
emitReplayState();
|
|
10975
|
+
return;
|
|
10976
|
+
}
|
|
10977
|
+
upsertBarInternal(point);
|
|
10978
|
+
};
|
|
10022
10979
|
const setPriceLines = (lines) => {
|
|
10023
10980
|
priceLines = lines.map((line, index) => ({
|
|
10024
10981
|
...line,
|
|
@@ -10207,6 +11164,96 @@ function createChart(element, options = {}) {
|
|
|
10207
11164
|
tradeMarkers = Array.isArray(markers) ? markers.slice() : [];
|
|
10208
11165
|
scheduleDraw();
|
|
10209
11166
|
};
|
|
11167
|
+
const setBarMarks = (marks) => {
|
|
11168
|
+
barMarks = Array.isArray(marks) ? marks.slice() : [];
|
|
11169
|
+
scheduleDraw();
|
|
11170
|
+
};
|
|
11171
|
+
const getBarMarks = () => barMarks.slice();
|
|
11172
|
+
const setTimescaleMarks = (marks) => {
|
|
11173
|
+
timescaleMarks = Array.isArray(marks) ? marks.slice() : [];
|
|
11174
|
+
scheduleDraw();
|
|
11175
|
+
};
|
|
11176
|
+
const getTimescaleMarks = () => timescaleMarks.slice();
|
|
11177
|
+
const onMarkClick = (handler) => {
|
|
11178
|
+
markClickHandler = handler;
|
|
11179
|
+
};
|
|
11180
|
+
const onMarkHover = (handler) => {
|
|
11181
|
+
markHoverHandler = handler;
|
|
11182
|
+
};
|
|
11183
|
+
const setAlerts = (nextAlerts) => {
|
|
11184
|
+
alerts = (Array.isArray(nextAlerts) ? nextAlerts : []).map((alert) => normalizeAlert(alert));
|
|
11185
|
+
const last = data[data.length - 1];
|
|
11186
|
+
if (last) {
|
|
11187
|
+
for (const alert of alerts) alert.lastPrice = last.c;
|
|
11188
|
+
}
|
|
11189
|
+
resetRightMarginCache();
|
|
11190
|
+
scheduleDraw();
|
|
11191
|
+
};
|
|
11192
|
+
const addAlert = (alert) => {
|
|
11193
|
+
const next = normalizeAlert(alert);
|
|
11194
|
+
const last = data[data.length - 1];
|
|
11195
|
+
if (last) next.lastPrice = last.c;
|
|
11196
|
+
alerts.push(next);
|
|
11197
|
+
resetRightMarginCache();
|
|
11198
|
+
scheduleDraw();
|
|
11199
|
+
return next.id;
|
|
11200
|
+
};
|
|
11201
|
+
const updateAlert = (id, patch) => {
|
|
11202
|
+
const target = alerts.find((alert) => alert.id === id);
|
|
11203
|
+
if (!target) return;
|
|
11204
|
+
if (Number.isFinite(patch.price)) target.price = Number(patch.price);
|
|
11205
|
+
if (patch.condition) target.condition = patch.condition;
|
|
11206
|
+
if (patch.label !== void 0) target.label = patch.label;
|
|
11207
|
+
if (patch.color !== void 0) target.color = patch.color;
|
|
11208
|
+
if (patch.active !== void 0) target.active = patch.active;
|
|
11209
|
+
if (patch.once !== void 0) target.once = patch.once;
|
|
11210
|
+
if (patch.draggable !== void 0) target.draggable = patch.draggable;
|
|
11211
|
+
if (patch.triggered !== void 0) {
|
|
11212
|
+
target.triggered = patch.triggered;
|
|
11213
|
+
if (!patch.triggered) target.lastPrice = data[data.length - 1]?.c ?? null;
|
|
11214
|
+
}
|
|
11215
|
+
resetRightMarginCache();
|
|
11216
|
+
scheduleDraw();
|
|
11217
|
+
};
|
|
11218
|
+
const removeAlert = (id) => {
|
|
11219
|
+
alerts = alerts.filter((alert) => alert.id !== id);
|
|
11220
|
+
if (hoveredAlertId === id) hoveredAlertId = null;
|
|
11221
|
+
resetRightMarginCache();
|
|
11222
|
+
scheduleDraw();
|
|
11223
|
+
};
|
|
11224
|
+
const getAlerts = () => alerts.map((alert) => serializeAlert(alert));
|
|
11225
|
+
const onAlertTrigger = (handler) => {
|
|
11226
|
+
alertTriggerHandler = handler;
|
|
11227
|
+
};
|
|
11228
|
+
const onAlertAction = (handler) => {
|
|
11229
|
+
alertActionHandler = handler;
|
|
11230
|
+
};
|
|
11231
|
+
const setTimezone = (timezone) => {
|
|
11232
|
+
timezoneSetting = timezone || "local";
|
|
11233
|
+
zoneClock = createZoneClock(timezoneSetting);
|
|
11234
|
+
scheduleDraw();
|
|
11235
|
+
};
|
|
11236
|
+
const getTimezone = () => timezoneSetting;
|
|
11237
|
+
const setTimeFormat = (format) => {
|
|
11238
|
+
timeFormat = format === "12h" ? "12h" : "24h";
|
|
11239
|
+
resetRightMarginCache();
|
|
11240
|
+
scheduleDraw();
|
|
11241
|
+
};
|
|
11242
|
+
const getTimeFormat = () => timeFormat;
|
|
11243
|
+
const setSession = (session) => {
|
|
11244
|
+
if (session === null) {
|
|
11245
|
+
sessionOptions = { ...sessionOptions, spec: null };
|
|
11246
|
+
} else if (typeof session === "string") {
|
|
11247
|
+
sessionOptions = { ...sessionOptions, spec: session };
|
|
11248
|
+
} else if ("segments" in session || "timezone" in session || "days" in session) {
|
|
11249
|
+
sessionOptions = { ...sessionOptions, spec: session };
|
|
11250
|
+
} else {
|
|
11251
|
+
sessionOptions = { ...sessionOptions, ...session };
|
|
11252
|
+
}
|
|
11253
|
+
compiledSession = compileSession(sessionOptions.spec);
|
|
11254
|
+
scheduleDraw();
|
|
11255
|
+
};
|
|
11256
|
+
const getSession = () => ({ ...sessionOptions });
|
|
10210
11257
|
const setMagnetMode = (mode) => {
|
|
10211
11258
|
magnetMode = mode;
|
|
10212
11259
|
};
|
|
@@ -10226,7 +11273,13 @@ function createChart(element, options = {}) {
|
|
|
10226
11273
|
drawings = dedupeDrawingIds(nextDrawings.map((drawing) => normalizeDrawingState(drawing)));
|
|
10227
11274
|
draftDrawing = null;
|
|
10228
11275
|
reanchorDrawingsToData();
|
|
10229
|
-
|
|
11276
|
+
historySuspended += 1;
|
|
11277
|
+
try {
|
|
11278
|
+
emitDrawingsChange();
|
|
11279
|
+
} finally {
|
|
11280
|
+
historySuspended -= 1;
|
|
11281
|
+
}
|
|
11282
|
+
resetHistoryBaseline();
|
|
10230
11283
|
scheduleDraw();
|
|
10231
11284
|
};
|
|
10232
11285
|
const addDrawing = (drawing) => {
|
|
@@ -10311,7 +11364,10 @@ function createChart(element, options = {}) {
|
|
|
10311
11364
|
indicators: getIndicators(),
|
|
10312
11365
|
magnetMode: getMagnetMode(),
|
|
10313
11366
|
priceScale: getPriceScale(),
|
|
10314
|
-
drawingDefaults
|
|
11367
|
+
drawingDefaults,
|
|
11368
|
+
alerts: getAlerts(),
|
|
11369
|
+
timezone: getTimezone(),
|
|
11370
|
+
timeFormat: getTimeFormat()
|
|
10315
11371
|
};
|
|
10316
11372
|
};
|
|
10317
11373
|
const loadState = (state) => {
|
|
@@ -10340,9 +11396,19 @@ function createChart(element, options = {}) {
|
|
|
10340
11396
|
}
|
|
10341
11397
|
}
|
|
10342
11398
|
}
|
|
11399
|
+
if (Array.isArray(state.alerts)) {
|
|
11400
|
+
setAlerts(state.alerts);
|
|
11401
|
+
}
|
|
11402
|
+
if (typeof state.timezone === "string") {
|
|
11403
|
+
setTimezone(state.timezone);
|
|
11404
|
+
}
|
|
11405
|
+
if (state.timeFormat === "12h" || state.timeFormat === "24h") {
|
|
11406
|
+
setTimeFormat(state.timeFormat);
|
|
11407
|
+
}
|
|
10343
11408
|
if (state.viewport && typeof state.viewport === "object") {
|
|
10344
11409
|
setViewport(state.viewport);
|
|
10345
11410
|
}
|
|
11411
|
+
clearHistory();
|
|
10346
11412
|
};
|
|
10347
11413
|
const takeScreenshot = (options2 = {}) => {
|
|
10348
11414
|
return canvas.toDataURL(options2.type ?? "image/png", options2.quality);
|
|
@@ -10350,6 +11416,8 @@ function createChart(element, options = {}) {
|
|
|
10350
11416
|
const destroy = () => {
|
|
10351
11417
|
cancelViewportTween();
|
|
10352
11418
|
cancelLongPressTimer();
|
|
11419
|
+
clearReplayTimer();
|
|
11420
|
+
replay = null;
|
|
10353
11421
|
datafeedUnsubscribe?.();
|
|
10354
11422
|
datafeedUnsubscribe = null;
|
|
10355
11423
|
datafeed = null;
|
|
@@ -10383,6 +11451,10 @@ function createChart(element, options = {}) {
|
|
|
10383
11451
|
}
|
|
10384
11452
|
element.innerHTML = "";
|
|
10385
11453
|
};
|
|
11454
|
+
if (Array.isArray(options.alerts) && options.alerts.length > 0) {
|
|
11455
|
+
setAlerts(options.alerts);
|
|
11456
|
+
}
|
|
11457
|
+
resetHistoryBaseline();
|
|
10386
11458
|
draw();
|
|
10387
11459
|
armCountdownHeartbeat();
|
|
10388
11460
|
return {
|
|
@@ -10466,6 +11538,42 @@ function createChart(element, options = {}) {
|
|
|
10466
11538
|
saveState,
|
|
10467
11539
|
loadState,
|
|
10468
11540
|
takeScreenshot,
|
|
11541
|
+
setTimezone,
|
|
11542
|
+
getTimezone,
|
|
11543
|
+
setTimeFormat,
|
|
11544
|
+
getTimeFormat,
|
|
11545
|
+
setSession,
|
|
11546
|
+
getSession,
|
|
11547
|
+
undo,
|
|
11548
|
+
redo,
|
|
11549
|
+
canUndo: () => historyPast.length > 0,
|
|
11550
|
+
canRedo: () => historyFuture.length > 0,
|
|
11551
|
+
getUndoRedoState,
|
|
11552
|
+
onUndoRedoStateChange: (handler) => {
|
|
11553
|
+
undoRedoStateHandler = handler;
|
|
11554
|
+
},
|
|
11555
|
+
clearHistory,
|
|
11556
|
+
startReplay,
|
|
11557
|
+
stopReplay,
|
|
11558
|
+
replayStep,
|
|
11559
|
+
replayPlay,
|
|
11560
|
+
replayPause,
|
|
11561
|
+
setReplaySpeed,
|
|
11562
|
+
getReplayState,
|
|
11563
|
+
onReplayStateChange,
|
|
11564
|
+
setAlerts,
|
|
11565
|
+
addAlert,
|
|
11566
|
+
updateAlert,
|
|
11567
|
+
removeAlert,
|
|
11568
|
+
getAlerts,
|
|
11569
|
+
onAlertTrigger,
|
|
11570
|
+
onAlertAction,
|
|
11571
|
+
setBarMarks,
|
|
11572
|
+
getBarMarks,
|
|
11573
|
+
setTimescaleMarks,
|
|
11574
|
+
getTimescaleMarks,
|
|
11575
|
+
onMarkClick,
|
|
11576
|
+
onMarkHover,
|
|
10469
11577
|
resize,
|
|
10470
11578
|
destroy
|
|
10471
11579
|
};
|
|
@@ -10476,8 +11584,10 @@ export {
|
|
|
10476
11584
|
FIB_DEFAULT_PALETTE,
|
|
10477
11585
|
POSITION_DEFAULT_COLORS,
|
|
10478
11586
|
SCRIPT_SOURCE_INPUT_VALUES,
|
|
11587
|
+
SESSION_PRESETS,
|
|
10479
11588
|
compileScriptIndicator,
|
|
10480
11589
|
createChart,
|
|
10481
11590
|
extractScriptInputs,
|
|
11591
|
+
isValidTimeZone,
|
|
10482
11592
|
validateScriptSource
|
|
10483
11593
|
};
|