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
package/dist/index.cjs
CHANGED
|
@@ -25,9 +25,11 @@ __export(index_exports, {
|
|
|
25
25
|
FIB_DEFAULT_PALETTE: () => FIB_DEFAULT_PALETTE,
|
|
26
26
|
POSITION_DEFAULT_COLORS: () => POSITION_DEFAULT_COLORS,
|
|
27
27
|
SCRIPT_SOURCE_INPUT_VALUES: () => SCRIPT_SOURCE_INPUT_VALUES,
|
|
28
|
+
SESSION_PRESETS: () => SESSION_PRESETS,
|
|
28
29
|
compileScriptIndicator: () => compileScriptIndicator,
|
|
29
30
|
createChart: () => createChart,
|
|
30
31
|
extractScriptInputs: () => extractScriptInputs,
|
|
32
|
+
isValidTimeZone: () => isValidTimeZone,
|
|
31
33
|
validateScriptSource: () => validateScriptSource
|
|
32
34
|
});
|
|
33
35
|
module.exports = __toCommonJS(index_exports);
|
|
@@ -2777,6 +2779,237 @@ var CHART_THEMES = {
|
|
|
2777
2779
|
var CHART_THEME_NAMES = Object.keys(CHART_THEMES);
|
|
2778
2780
|
var resolveTheme = (theme) => typeof theme === "string" ? CHART_THEMES[theme] ?? CHART_THEMES.midnight : theme;
|
|
2779
2781
|
|
|
2782
|
+
// src/time.ts
|
|
2783
|
+
var DAY_MS = 864e5;
|
|
2784
|
+
var HOUR_MS = 36e5;
|
|
2785
|
+
var OFFSET_FORMATTER_CACHE = /* @__PURE__ */ new Map();
|
|
2786
|
+
var LABEL_FORMATTER_CACHE = /* @__PURE__ */ new Map();
|
|
2787
|
+
var getOffsetFormatter = (timeZone) => {
|
|
2788
|
+
let formatter = OFFSET_FORMATTER_CACHE.get(timeZone);
|
|
2789
|
+
if (!formatter) {
|
|
2790
|
+
formatter = new Intl.DateTimeFormat("en-US", {
|
|
2791
|
+
timeZone,
|
|
2792
|
+
hourCycle: "h23",
|
|
2793
|
+
year: "numeric",
|
|
2794
|
+
month: "2-digit",
|
|
2795
|
+
day: "2-digit",
|
|
2796
|
+
hour: "2-digit",
|
|
2797
|
+
minute: "2-digit",
|
|
2798
|
+
second: "2-digit"
|
|
2799
|
+
});
|
|
2800
|
+
OFFSET_FORMATTER_CACHE.set(timeZone, formatter);
|
|
2801
|
+
}
|
|
2802
|
+
return formatter;
|
|
2803
|
+
};
|
|
2804
|
+
var getLabelFormatter = (timeZone, options, key) => {
|
|
2805
|
+
const cacheKey = `${timeZone ?? "local"}|${key}`;
|
|
2806
|
+
let formatter = LABEL_FORMATTER_CACHE.get(cacheKey);
|
|
2807
|
+
if (!formatter) {
|
|
2808
|
+
formatter = new Intl.DateTimeFormat(void 0, timeZone ? { ...options, timeZone } : options);
|
|
2809
|
+
LABEL_FORMATTER_CACHE.set(cacheKey, formatter);
|
|
2810
|
+
}
|
|
2811
|
+
return formatter;
|
|
2812
|
+
};
|
|
2813
|
+
var isValidTimeZone = (timeZone) => {
|
|
2814
|
+
try {
|
|
2815
|
+
new Intl.DateTimeFormat("en-US", { timeZone });
|
|
2816
|
+
return true;
|
|
2817
|
+
} catch {
|
|
2818
|
+
return false;
|
|
2819
|
+
}
|
|
2820
|
+
};
|
|
2821
|
+
var floorDiv = (value, divisor) => Math.floor(value / divisor);
|
|
2822
|
+
var positiveMod = (value, divisor) => (value % divisor + divisor) % divisor;
|
|
2823
|
+
var computeOffsetMs = (timeZone, ms) => {
|
|
2824
|
+
const parts = getOffsetFormatter(timeZone).formatToParts(new Date(ms));
|
|
2825
|
+
let year = 0;
|
|
2826
|
+
let month = 1;
|
|
2827
|
+
let day = 1;
|
|
2828
|
+
let hour = 0;
|
|
2829
|
+
let minute = 0;
|
|
2830
|
+
let second = 0;
|
|
2831
|
+
for (const part of parts) {
|
|
2832
|
+
const value = Number(part.value);
|
|
2833
|
+
if (part.type === "year") year = value;
|
|
2834
|
+
else if (part.type === "month") month = value;
|
|
2835
|
+
else if (part.type === "day") day = value;
|
|
2836
|
+
else if (part.type === "hour") hour = value === 24 ? 0 : value;
|
|
2837
|
+
else if (part.type === "minute") minute = value;
|
|
2838
|
+
else if (part.type === "second") second = value;
|
|
2839
|
+
}
|
|
2840
|
+
const asUtc = Date.UTC(year, month - 1, day, hour, minute, second);
|
|
2841
|
+
return asUtc - Math.floor(ms / 1e3) * 1e3;
|
|
2842
|
+
};
|
|
2843
|
+
var createZoneClock = (timeZone) => {
|
|
2844
|
+
const normalized = !timeZone || timeZone === "local" ? null : timeZone.toLowerCase() === "utc" ? "UTC" : isValidTimeZone(timeZone) ? timeZone : null;
|
|
2845
|
+
if (normalized === null) {
|
|
2846
|
+
const scratch = /* @__PURE__ */ new Date();
|
|
2847
|
+
const partsOf2 = (ms) => {
|
|
2848
|
+
scratch.setTime(ms);
|
|
2849
|
+
return {
|
|
2850
|
+
year: scratch.getFullYear(),
|
|
2851
|
+
month: scratch.getMonth() + 1,
|
|
2852
|
+
day: scratch.getDate(),
|
|
2853
|
+
hour: scratch.getHours(),
|
|
2854
|
+
minute: scratch.getMinutes()
|
|
2855
|
+
};
|
|
2856
|
+
};
|
|
2857
|
+
return {
|
|
2858
|
+
timeZone: null,
|
|
2859
|
+
offsetMs: (ms) => {
|
|
2860
|
+
scratch.setTime(ms);
|
|
2861
|
+
return -scratch.getTimezoneOffset() * 6e4;
|
|
2862
|
+
},
|
|
2863
|
+
dayKey: (ms) => {
|
|
2864
|
+
scratch.setTime(ms);
|
|
2865
|
+
return floorDiv(Date.UTC(scratch.getFullYear(), scratch.getMonth(), scratch.getDate()), DAY_MS);
|
|
2866
|
+
},
|
|
2867
|
+
minutesOfDay: (ms) => {
|
|
2868
|
+
scratch.setTime(ms);
|
|
2869
|
+
return scratch.getHours() * 60 + scratch.getMinutes();
|
|
2870
|
+
},
|
|
2871
|
+
weekday: (ms) => {
|
|
2872
|
+
scratch.setTime(ms);
|
|
2873
|
+
return scratch.getDay();
|
|
2874
|
+
},
|
|
2875
|
+
parts: partsOf2,
|
|
2876
|
+
formatTime: (ms, hour12) => formatClockTime(partsOf2(ms), hour12),
|
|
2877
|
+
formatDayMonth: (ms) => getLabelFormatter(null, { month: "short", day: "numeric" }, "md").format(ms),
|
|
2878
|
+
formatMonth: (ms) => getLabelFormatter(null, { month: "short" }, "m").format(ms),
|
|
2879
|
+
formatYear: (ms) => String(partsOf2(ms).year)
|
|
2880
|
+
};
|
|
2881
|
+
}
|
|
2882
|
+
const zone = normalized;
|
|
2883
|
+
const offsetCache = /* @__PURE__ */ new Map();
|
|
2884
|
+
const offsetMs = (ms) => {
|
|
2885
|
+
const bucket = floorDiv(ms, HOUR_MS);
|
|
2886
|
+
const cached = offsetCache.get(bucket);
|
|
2887
|
+
if (cached !== void 0) return cached;
|
|
2888
|
+
const offset = computeOffsetMs(zone, ms);
|
|
2889
|
+
if (offsetCache.size > 2e4) offsetCache.clear();
|
|
2890
|
+
offsetCache.set(bucket, offset);
|
|
2891
|
+
return offset;
|
|
2892
|
+
};
|
|
2893
|
+
const localMs = (ms) => ms + offsetMs(ms);
|
|
2894
|
+
const partsOf = (ms) => {
|
|
2895
|
+
const local = new Date(localMs(ms));
|
|
2896
|
+
return {
|
|
2897
|
+
year: local.getUTCFullYear(),
|
|
2898
|
+
month: local.getUTCMonth() + 1,
|
|
2899
|
+
day: local.getUTCDate(),
|
|
2900
|
+
hour: local.getUTCHours(),
|
|
2901
|
+
minute: local.getUTCMinutes()
|
|
2902
|
+
};
|
|
2903
|
+
};
|
|
2904
|
+
return {
|
|
2905
|
+
timeZone: zone,
|
|
2906
|
+
offsetMs,
|
|
2907
|
+
dayKey: (ms) => floorDiv(localMs(ms), DAY_MS),
|
|
2908
|
+
minutesOfDay: (ms) => floorDiv(positiveMod(localMs(ms), DAY_MS), 6e4),
|
|
2909
|
+
// 1970-01-01 was a Thursday (index 4).
|
|
2910
|
+
weekday: (ms) => positiveMod(floorDiv(localMs(ms), DAY_MS) + 4, 7),
|
|
2911
|
+
parts: partsOf,
|
|
2912
|
+
formatTime: (ms, hour12) => formatClockTime(partsOf(ms), hour12),
|
|
2913
|
+
formatDayMonth: (ms) => getLabelFormatter(zone, { month: "short", day: "numeric" }, "md").format(ms),
|
|
2914
|
+
formatMonth: (ms) => getLabelFormatter(zone, { month: "short" }, "m").format(ms),
|
|
2915
|
+
formatYear: (ms) => String(partsOf(ms).year)
|
|
2916
|
+
};
|
|
2917
|
+
};
|
|
2918
|
+
var pad2 = (value) => value < 10 ? `0${value}` : String(value);
|
|
2919
|
+
var formatClockTime = (parts, hour12) => {
|
|
2920
|
+
if (!hour12) return `${pad2(parts.hour)}:${pad2(parts.minute)}`;
|
|
2921
|
+
const suffix = parts.hour < 12 ? "AM" : "PM";
|
|
2922
|
+
const hour = parts.hour % 12 === 0 ? 12 : parts.hour % 12;
|
|
2923
|
+
return `${hour}:${pad2(parts.minute)} ${suffix}`;
|
|
2924
|
+
};
|
|
2925
|
+
var parseHhMm = (value) => {
|
|
2926
|
+
const match = /^(\d{1,2}):?(\d{2})$/.exec(value.trim());
|
|
2927
|
+
if (!match) return null;
|
|
2928
|
+
const hour = Number(match[1]);
|
|
2929
|
+
const minute = Number(match[2]);
|
|
2930
|
+
if (!Number.isFinite(hour) || !Number.isFinite(minute)) return null;
|
|
2931
|
+
if (hour < 0 || hour > 24 || minute < 0 || minute > 59) return null;
|
|
2932
|
+
return hour * 60 + minute;
|
|
2933
|
+
};
|
|
2934
|
+
var SESSION_PRESETS = {
|
|
2935
|
+
"cme-futures": {
|
|
2936
|
+
timezone: "America/New_York",
|
|
2937
|
+
// Opening days: the session opens Sunday–Thursday evening and runs into
|
|
2938
|
+
// the following morning, so Friday evening (weekend) is correctly closed.
|
|
2939
|
+
days: [0, 1, 2, 3, 4],
|
|
2940
|
+
segments: [{ start: "18:00", end: "17:00" }]
|
|
2941
|
+
},
|
|
2942
|
+
"cme-rth": {
|
|
2943
|
+
timezone: "America/New_York",
|
|
2944
|
+
days: [1, 2, 3, 4, 5],
|
|
2945
|
+
segments: [{ start: "09:30", end: "16:00" }]
|
|
2946
|
+
},
|
|
2947
|
+
"us-equities": {
|
|
2948
|
+
timezone: "America/New_York",
|
|
2949
|
+
days: [1, 2, 3, 4, 5],
|
|
2950
|
+
segments: [{ start: "09:30", end: "16:00" }]
|
|
2951
|
+
},
|
|
2952
|
+
"us-equities-extended": {
|
|
2953
|
+
timezone: "America/New_York",
|
|
2954
|
+
days: [1, 2, 3, 4, 5],
|
|
2955
|
+
segments: [{ start: "04:00", end: "20:00" }]
|
|
2956
|
+
},
|
|
2957
|
+
"24x7": {
|
|
2958
|
+
days: [0, 1, 2, 3, 4, 5, 6],
|
|
2959
|
+
segments: [{ start: "00:00", end: "24:00" }]
|
|
2960
|
+
}
|
|
2961
|
+
};
|
|
2962
|
+
var resolveSessionSpec = (spec) => {
|
|
2963
|
+
if (!spec) return null;
|
|
2964
|
+
if (typeof spec === "string") return SESSION_PRESETS[spec] ?? null;
|
|
2965
|
+
return spec;
|
|
2966
|
+
};
|
|
2967
|
+
var compileSession = (spec) => {
|
|
2968
|
+
const resolved = resolveSessionSpec(spec);
|
|
2969
|
+
if (!resolved) return null;
|
|
2970
|
+
const rawSegments = resolved.segments?.length ? resolved.segments : [{ start: "00:00", end: "24:00" }];
|
|
2971
|
+
const segments = [];
|
|
2972
|
+
for (const segment of rawSegments) {
|
|
2973
|
+
const startMinutes = parseHhMm(segment.start);
|
|
2974
|
+
const endMinutesRaw = parseHhMm(segment.end);
|
|
2975
|
+
if (startMinutes === null || endMinutesRaw === null) continue;
|
|
2976
|
+
const endMinutes = endMinutesRaw === 0 ? 1440 : endMinutesRaw;
|
|
2977
|
+
segments.push({ startMinutes, endMinutes, wraps: endMinutes <= startMinutes });
|
|
2978
|
+
}
|
|
2979
|
+
if (segments.length === 0) return null;
|
|
2980
|
+
return {
|
|
2981
|
+
clock: createZoneClock(resolved.timezone ?? null),
|
|
2982
|
+
days: new Set(resolved.days ?? [1, 2, 3, 4, 5]),
|
|
2983
|
+
segments,
|
|
2984
|
+
overnight: segments.some((segment) => segment.wraps)
|
|
2985
|
+
};
|
|
2986
|
+
};
|
|
2987
|
+
var sessionInfoAt = (session, ms) => {
|
|
2988
|
+
const { clock } = session;
|
|
2989
|
+
const dayKey = clock.dayKey(ms);
|
|
2990
|
+
const minutes = clock.minutesOfDay(ms);
|
|
2991
|
+
const weekday = clock.weekday(ms);
|
|
2992
|
+
let sessionDay = dayKey;
|
|
2993
|
+
let inSession = false;
|
|
2994
|
+
for (const segment of session.segments) {
|
|
2995
|
+
if (segment.wraps) {
|
|
2996
|
+
if (minutes >= segment.startMinutes) {
|
|
2997
|
+
if (session.days.has(weekday)) {
|
|
2998
|
+
inSession = true;
|
|
2999
|
+
sessionDay = dayKey + 1;
|
|
3000
|
+
}
|
|
3001
|
+
} else if (minutes < segment.endMinutes) {
|
|
3002
|
+
const openedWeekday = positiveMod(weekday - 1, 7);
|
|
3003
|
+
if (session.days.has(openedWeekday)) inSession = true;
|
|
3004
|
+
sessionDay = dayKey;
|
|
3005
|
+
}
|
|
3006
|
+
} else if (minutes >= segment.startMinutes && minutes < segment.endMinutes && session.days.has(weekday)) {
|
|
3007
|
+
inSession = true;
|
|
3008
|
+
}
|
|
3009
|
+
}
|
|
3010
|
+
return { inSession, sessionDay };
|
|
3011
|
+
};
|
|
3012
|
+
|
|
2780
3013
|
// src/options.ts
|
|
2781
3014
|
var DEFAULT_GRID_OPTIONS = {
|
|
2782
3015
|
color: "#2b2f38",
|
|
@@ -2994,9 +3227,19 @@ var DEFAULT_OPTIONS = {
|
|
|
2994
3227
|
datafeedOptions: { prefetchThresholdBars: 150, cooldownMs: 1200, chunkBars: 1500 },
|
|
2995
3228
|
animation: { viewportTransitions: true, durationMs: 260 },
|
|
2996
3229
|
indicatorUpdate: { liveThrottleMs: 80 },
|
|
3230
|
+
timezone: "local",
|
|
3231
|
+
timeFormat: "24h",
|
|
3232
|
+
session: {
|
|
3233
|
+
spec: null,
|
|
3234
|
+
separators: true,
|
|
3235
|
+
highlightOutOfSession: false,
|
|
3236
|
+
outOfSessionColor: "rgba(120,132,158,0.07)"
|
|
3237
|
+
},
|
|
3238
|
+
history: { enabled: true, limit: 100 },
|
|
2997
3239
|
crosshair: DEFAULT_CROSSHAIR_OPTIONS,
|
|
2998
3240
|
grid: DEFAULT_GRID_OPTIONS,
|
|
2999
3241
|
watermark: DEFAULT_WATERMARK_OPTIONS,
|
|
3242
|
+
alerts: [],
|
|
3000
3243
|
priceLines: [],
|
|
3001
3244
|
orderLines: [],
|
|
3002
3245
|
tickerLine: {
|
|
@@ -3064,6 +3307,14 @@ var mergeChartOptions = (baseOptions, options = {}) => ({
|
|
|
3064
3307
|
...baseOptions.indicatorUpdate,
|
|
3065
3308
|
...options.indicatorUpdate ?? {}
|
|
3066
3309
|
},
|
|
3310
|
+
session: {
|
|
3311
|
+
...baseOptions.session,
|
|
3312
|
+
...options.session ?? {}
|
|
3313
|
+
},
|
|
3314
|
+
history: {
|
|
3315
|
+
...baseOptions.history,
|
|
3316
|
+
...options.history ?? {}
|
|
3317
|
+
},
|
|
3067
3318
|
datafeedOptions: {
|
|
3068
3319
|
...baseOptions.datafeedOptions,
|
|
3069
3320
|
...options.datafeedOptions ?? {}
|
|
@@ -3227,6 +3478,45 @@ function createChart(element, options = {}) {
|
|
|
3227
3478
|
...drawing.label === void 0 ? {} : { label: drawing.label }
|
|
3228
3479
|
});
|
|
3229
3480
|
let tradeMarkers = [];
|
|
3481
|
+
let timezoneSetting = mergedOptions.timezone ?? "local";
|
|
3482
|
+
let zoneClock = createZoneClock(timezoneSetting);
|
|
3483
|
+
let timeFormat = mergedOptions.timeFormat === "12h" ? "12h" : "24h";
|
|
3484
|
+
const DEFAULT_SESSION_OPTIONS = {
|
|
3485
|
+
spec: null,
|
|
3486
|
+
separators: true,
|
|
3487
|
+
highlightOutOfSession: false,
|
|
3488
|
+
outOfSessionColor: "rgba(120,132,158,0.07)"
|
|
3489
|
+
};
|
|
3490
|
+
let sessionOptions = {
|
|
3491
|
+
...DEFAULT_SESSION_OPTIONS,
|
|
3492
|
+
...mergedOptions.session ?? {}
|
|
3493
|
+
};
|
|
3494
|
+
let compiledSession = compileSession(sessionOptions.spec);
|
|
3495
|
+
const isHour12 = () => timeFormat === "12h";
|
|
3496
|
+
const formatClock = (time) => zoneClock.formatTime(time.getTime(), isHour12());
|
|
3497
|
+
let barMarks = [];
|
|
3498
|
+
let timescaleMarks = [];
|
|
3499
|
+
let markRegions = [];
|
|
3500
|
+
let hoveredMarkKey = null;
|
|
3501
|
+
let markClickHandler = null;
|
|
3502
|
+
let markHoverHandler = null;
|
|
3503
|
+
let alerts = [];
|
|
3504
|
+
let generatedAlertId = 1;
|
|
3505
|
+
let alertTriggerHandler = null;
|
|
3506
|
+
let alertActionHandler = null;
|
|
3507
|
+
let alertRegions = [];
|
|
3508
|
+
let alertDragState = null;
|
|
3509
|
+
let hoveredAlertId = null;
|
|
3510
|
+
let replay = null;
|
|
3511
|
+
let replayStateHandler = null;
|
|
3512
|
+
const historyEnabled = mergedOptions.history?.enabled !== false;
|
|
3513
|
+
const historyLimit = Math.max(1, Math.floor(Number(mergedOptions.history?.limit ?? 100)) || 100);
|
|
3514
|
+
let historyPast = [];
|
|
3515
|
+
let historyFuture = [];
|
|
3516
|
+
let historyBaseline = "";
|
|
3517
|
+
let historySuspended = 0;
|
|
3518
|
+
let undoRedoStateHandler = null;
|
|
3519
|
+
let lastUndoRedoSignature = "";
|
|
3230
3520
|
let indicators = (options.indicators ?? []).map((indicator) => normalizeIndicatorState(indicator));
|
|
3231
3521
|
let drawings = dedupeDrawingIds(
|
|
3232
3522
|
(options.drawings ?? []).map((drawing) => normalizeDrawingState(drawing))
|
|
@@ -3275,9 +3565,168 @@ function createChart(element, options = {}) {
|
|
|
3275
3565
|
let selectedDrawingId = null;
|
|
3276
3566
|
const drawingToolDefaults = /* @__PURE__ */ new Map();
|
|
3277
3567
|
const getDrawingToolDefaults = (tool) => drawingToolDefaults.get(tool) ?? {};
|
|
3568
|
+
const historyDoc = () => JSON.stringify(drawings.map((drawing) => serializeDrawing(drawing)));
|
|
3569
|
+
const describeHistoryChange = (beforeDoc, afterDoc) => {
|
|
3570
|
+
let before = [];
|
|
3571
|
+
let after = [];
|
|
3572
|
+
try {
|
|
3573
|
+
before = JSON.parse(beforeDoc);
|
|
3574
|
+
after = JSON.parse(afterDoc);
|
|
3575
|
+
} catch {
|
|
3576
|
+
return "Edit drawings";
|
|
3577
|
+
}
|
|
3578
|
+
const label = (type) => type ? type.replace(/-/g, " ") : "drawing";
|
|
3579
|
+
if (after.length > before.length) {
|
|
3580
|
+
const beforeIds = new Set(before.map((drawing) => drawing.id));
|
|
3581
|
+
const added = after.find((drawing) => !beforeIds.has(drawing.id));
|
|
3582
|
+
return `Add ${label(added?.type)}`;
|
|
3583
|
+
}
|
|
3584
|
+
if (after.length < before.length) {
|
|
3585
|
+
if (after.length === 0 && before.length > 1) return "Clear drawings";
|
|
3586
|
+
const afterIds = new Set(after.map((drawing) => drawing.id));
|
|
3587
|
+
const removed = before.find((drawing) => !afterIds.has(drawing.id));
|
|
3588
|
+
return `Delete ${label(removed?.type)}`;
|
|
3589
|
+
}
|
|
3590
|
+
const changed = after.find((drawing, index) => JSON.stringify(drawing) !== JSON.stringify(before[index]));
|
|
3591
|
+
return `Edit ${label(changed?.type)}`;
|
|
3592
|
+
};
|
|
3593
|
+
const getUndoRedoState = () => ({
|
|
3594
|
+
canUndo: historyPast.length > 0,
|
|
3595
|
+
canRedo: historyFuture.length > 0,
|
|
3596
|
+
undoLabel: historyPast.length > 0 ? historyPast[historyPast.length - 1].label : null,
|
|
3597
|
+
redoLabel: historyFuture.length > 0 ? historyFuture[historyFuture.length - 1].label : null
|
|
3598
|
+
});
|
|
3599
|
+
const emitUndoRedoState = () => {
|
|
3600
|
+
const state = getUndoRedoState();
|
|
3601
|
+
const signature = `${state.canUndo}|${state.canRedo}|${state.undoLabel}|${state.redoLabel}`;
|
|
3602
|
+
if (signature === lastUndoRedoSignature) return;
|
|
3603
|
+
lastUndoRedoSignature = signature;
|
|
3604
|
+
undoRedoStateHandler?.(state);
|
|
3605
|
+
};
|
|
3606
|
+
const commitHistory = () => {
|
|
3607
|
+
if (!historyEnabled || historySuspended > 0) return;
|
|
3608
|
+
if (drawingDragState !== null) return;
|
|
3609
|
+
const next = historyDoc();
|
|
3610
|
+
if (next === historyBaseline) return;
|
|
3611
|
+
historyPast.push({ label: describeHistoryChange(historyBaseline, next), doc: historyBaseline });
|
|
3612
|
+
if (historyPast.length > historyLimit) historyPast.shift();
|
|
3613
|
+
historyFuture = [];
|
|
3614
|
+
historyBaseline = next;
|
|
3615
|
+
emitUndoRedoState();
|
|
3616
|
+
};
|
|
3617
|
+
const resetHistoryBaseline = () => {
|
|
3618
|
+
historyBaseline = historyDoc();
|
|
3619
|
+
};
|
|
3620
|
+
const applyHistoryDoc = (doc) => {
|
|
3621
|
+
let parsed;
|
|
3622
|
+
try {
|
|
3623
|
+
parsed = JSON.parse(doc);
|
|
3624
|
+
} catch {
|
|
3625
|
+
return;
|
|
3626
|
+
}
|
|
3627
|
+
historySuspended += 1;
|
|
3628
|
+
try {
|
|
3629
|
+
drawings = dedupeDrawingIds(parsed.map((drawing) => normalizeDrawingState(drawing)));
|
|
3630
|
+
if (selectedDrawingId !== null && !drawings.some((drawing) => drawing.id === selectedDrawingId)) {
|
|
3631
|
+
selectedDrawingId = null;
|
|
3632
|
+
}
|
|
3633
|
+
draftDrawing = null;
|
|
3634
|
+
drawingDragState = null;
|
|
3635
|
+
historyBaseline = doc;
|
|
3636
|
+
emitDrawingsChange();
|
|
3637
|
+
scheduleDraw({ updateAutoScale: true });
|
|
3638
|
+
} finally {
|
|
3639
|
+
historySuspended -= 1;
|
|
3640
|
+
}
|
|
3641
|
+
};
|
|
3642
|
+
const undo = () => {
|
|
3643
|
+
if (!historyEnabled || historyPast.length === 0) return false;
|
|
3644
|
+
const entry = historyPast.pop();
|
|
3645
|
+
historyFuture.push({ label: entry.label, doc: historyBaseline });
|
|
3646
|
+
applyHistoryDoc(entry.doc);
|
|
3647
|
+
emitUndoRedoState();
|
|
3648
|
+
return true;
|
|
3649
|
+
};
|
|
3650
|
+
const redo = () => {
|
|
3651
|
+
if (!historyEnabled || historyFuture.length === 0) return false;
|
|
3652
|
+
const entry = historyFuture.pop();
|
|
3653
|
+
historyPast.push({ label: entry.label, doc: historyBaseline });
|
|
3654
|
+
applyHistoryDoc(entry.doc);
|
|
3655
|
+
emitUndoRedoState();
|
|
3656
|
+
return true;
|
|
3657
|
+
};
|
|
3658
|
+
const clearHistory = () => {
|
|
3659
|
+
historyPast = [];
|
|
3660
|
+
historyFuture = [];
|
|
3661
|
+
resetHistoryBaseline();
|
|
3662
|
+
emitUndoRedoState();
|
|
3663
|
+
};
|
|
3278
3664
|
const emitDrawingsChange = () => {
|
|
3665
|
+
commitHistory();
|
|
3279
3666
|
drawingsChangeHandler?.(drawings.map((drawing) => serializeDrawing(drawing)));
|
|
3280
3667
|
};
|
|
3668
|
+
const normalizeAlert = (alert) => ({
|
|
3669
|
+
id: alert.id ?? `alert-${generatedAlertId++}`,
|
|
3670
|
+
price: Number(alert.price),
|
|
3671
|
+
condition: alert.condition ?? "crossing",
|
|
3672
|
+
label: alert.label ?? "",
|
|
3673
|
+
color: alert.color ?? "#f7a600",
|
|
3674
|
+
active: alert.active !== false,
|
|
3675
|
+
triggered: alert.triggered === true,
|
|
3676
|
+
once: alert.once !== false,
|
|
3677
|
+
draggable: alert.draggable !== false,
|
|
3678
|
+
lastPrice: null
|
|
3679
|
+
});
|
|
3680
|
+
const serializeAlert = (alert) => ({
|
|
3681
|
+
id: alert.id,
|
|
3682
|
+
price: alert.price,
|
|
3683
|
+
condition: alert.condition,
|
|
3684
|
+
label: alert.label,
|
|
3685
|
+
color: alert.color,
|
|
3686
|
+
active: alert.active,
|
|
3687
|
+
triggered: alert.triggered,
|
|
3688
|
+
once: alert.once,
|
|
3689
|
+
draggable: alert.draggable
|
|
3690
|
+
});
|
|
3691
|
+
const evaluateAlerts = (price, timeMs, index) => {
|
|
3692
|
+
if (alerts.length === 0 || !Number.isFinite(price)) return;
|
|
3693
|
+
for (const alert of alerts) {
|
|
3694
|
+
if (!alert.active || alert.triggered || !Number.isFinite(alert.price)) {
|
|
3695
|
+
alert.lastPrice = price;
|
|
3696
|
+
continue;
|
|
3697
|
+
}
|
|
3698
|
+
const previous = alert.lastPrice;
|
|
3699
|
+
let hit = false;
|
|
3700
|
+
switch (alert.condition) {
|
|
3701
|
+
case "greater":
|
|
3702
|
+
hit = price > alert.price;
|
|
3703
|
+
break;
|
|
3704
|
+
case "less":
|
|
3705
|
+
hit = price < alert.price;
|
|
3706
|
+
break;
|
|
3707
|
+
case "crossing-up":
|
|
3708
|
+
hit = previous !== null && previous < alert.price && price >= alert.price;
|
|
3709
|
+
break;
|
|
3710
|
+
case "crossing-down":
|
|
3711
|
+
hit = previous !== null && previous > alert.price && price <= alert.price;
|
|
3712
|
+
break;
|
|
3713
|
+
default:
|
|
3714
|
+
hit = previous !== null && (previous < alert.price && price >= alert.price || previous > alert.price && price <= alert.price);
|
|
3715
|
+
break;
|
|
3716
|
+
}
|
|
3717
|
+
alert.lastPrice = price;
|
|
3718
|
+
if (!hit) continue;
|
|
3719
|
+
alert.triggered = true;
|
|
3720
|
+
if (alert.once) alert.active = false;
|
|
3721
|
+
alertTriggerHandler?.({ alert: serializeAlert(alert), price, timeMs, index });
|
|
3722
|
+
scheduleDraw({ overlayOnly: false });
|
|
3723
|
+
}
|
|
3724
|
+
};
|
|
3725
|
+
const evaluateAlertsFromLatestBar = () => {
|
|
3726
|
+
if (alerts.length === 0 || data.length === 0) return;
|
|
3727
|
+
const last = data[data.length - 1];
|
|
3728
|
+
evaluateAlerts(last.c, last.time.getTime(), data.length - 1);
|
|
3729
|
+
};
|
|
3281
3730
|
const orderWidgetWidthById = /* @__PURE__ */ new Map();
|
|
3282
3731
|
const orderPriceTagWidthById = /* @__PURE__ */ new Map();
|
|
3283
3732
|
let xCenter = 0;
|
|
@@ -3450,6 +3899,9 @@ function createChart(element, options = {}) {
|
|
|
3450
3899
|
let overlayLayout = null;
|
|
3451
3900
|
const margin = { top: 16, right: 72, bottom: 26, left: 12 };
|
|
3452
3901
|
let cachedRightMargin = margin.right;
|
|
3902
|
+
let rightMarginShrinkPendingSince = null;
|
|
3903
|
+
const RIGHT_MARGIN_SHRINK_DELAY_MS = 600;
|
|
3904
|
+
const RIGHT_MARGIN_SHRINK_EPSILON_PX = 2;
|
|
3453
3905
|
const minVisibleBars = Math.max(1, Math.floor(mergedOptions.minVisibleBars));
|
|
3454
3906
|
const maxVisibleBars = Math.max(minVisibleBars, Math.floor(mergedOptions.maxVisibleBars));
|
|
3455
3907
|
const maxPanBars = Math.max(0, Math.floor(mergedOptions.maxPanBars));
|
|
@@ -3777,6 +4229,7 @@ function createChart(element, options = {}) {
|
|
|
3777
4229
|
};
|
|
3778
4230
|
const resetRightMarginCache = () => {
|
|
3779
4231
|
cachedRightMargin = margin.right;
|
|
4232
|
+
rightMarginShrinkPendingSince = null;
|
|
3780
4233
|
};
|
|
3781
4234
|
const parseData = (nextData) => {
|
|
3782
4235
|
const dedupedByTime = /* @__PURE__ */ new Map();
|
|
@@ -4139,40 +4592,13 @@ function createChart(element, options = {}) {
|
|
|
4139
4592
|
datafeedUnsubscribe = typeof unsubscribe === "function" ? unsubscribe : null;
|
|
4140
4593
|
};
|
|
4141
4594
|
const formatHoverTimeLabel = (time, mode) => {
|
|
4142
|
-
|
|
4143
|
-
|
|
4144
|
-
|
|
4145
|
-
|
|
4146
|
-
hour12: false
|
|
4147
|
-
});
|
|
4148
|
-
}
|
|
4149
|
-
if (mode === "datetime") {
|
|
4150
|
-
return time.toLocaleString(void 0, {
|
|
4151
|
-
month: "short",
|
|
4152
|
-
day: "numeric",
|
|
4153
|
-
hour: "2-digit",
|
|
4154
|
-
minute: "2-digit",
|
|
4155
|
-
hour12: false
|
|
4156
|
-
});
|
|
4157
|
-
}
|
|
4158
|
-
if (mode === "date") {
|
|
4159
|
-
return time.toLocaleDateString(void 0, {
|
|
4160
|
-
month: "short",
|
|
4161
|
-
day: "numeric"
|
|
4162
|
-
});
|
|
4163
|
-
}
|
|
4595
|
+
const ms = time.getTime();
|
|
4596
|
+
if (mode === "time") return formatClock(time);
|
|
4597
|
+
if (mode === "datetime") return `${zoneClock.formatDayMonth(ms)}, ${formatClock(time)}`;
|
|
4598
|
+
if (mode === "date") return zoneClock.formatDayMonth(ms);
|
|
4164
4599
|
const stepMs = getTimeStepMs();
|
|
4165
|
-
if (stepMs < 24 * 60 * 60 * 1e3)
|
|
4166
|
-
|
|
4167
|
-
hour: "2-digit",
|
|
4168
|
-
minute: "2-digit",
|
|
4169
|
-
hour12: false
|
|
4170
|
-
});
|
|
4171
|
-
}
|
|
4172
|
-
return time.toLocaleDateString(void 0, {
|
|
4173
|
-
month: "short",
|
|
4174
|
-
day: "numeric"
|
|
4175
|
-
});
|
|
4600
|
+
if (stepMs < 24 * 60 * 60 * 1e3) return formatClock(time);
|
|
4601
|
+
return zoneClock.formatDayMonth(ms);
|
|
4176
4602
|
};
|
|
4177
4603
|
const drawText = (text, x, y, align = "left", baseline = "alphabetic", color = mergedOptions.axis?.textColor ?? mergedOptions.axisColor) => {
|
|
4178
4604
|
ctx.fillStyle = color;
|
|
@@ -4269,6 +4695,86 @@ function createChart(element, options = {}) {
|
|
|
4269
4695
|
labelTextColorOn(mergedLine.labelBackgroundColor, mergedLine.labelTextColor)
|
|
4270
4696
|
);
|
|
4271
4697
|
};
|
|
4698
|
+
const drawBellGlyph = (x, y, size, color) => {
|
|
4699
|
+
const w = size;
|
|
4700
|
+
const h = size;
|
|
4701
|
+
ctx.save();
|
|
4702
|
+
ctx.strokeStyle = color;
|
|
4703
|
+
ctx.fillStyle = color;
|
|
4704
|
+
ctx.lineWidth = 1.2;
|
|
4705
|
+
ctx.lineJoin = "round";
|
|
4706
|
+
ctx.beginPath();
|
|
4707
|
+
ctx.moveTo(x - w * 0.42, y + h * 0.28);
|
|
4708
|
+
ctx.quadraticCurveTo(x - w * 0.3, y + h * 0.2, x - w * 0.3, y - h * 0.05);
|
|
4709
|
+
ctx.quadraticCurveTo(x - w * 0.3, y - h * 0.46, x, y - h * 0.46);
|
|
4710
|
+
ctx.quadraticCurveTo(x + w * 0.3, y - h * 0.46, x + w * 0.3, y - h * 0.05);
|
|
4711
|
+
ctx.quadraticCurveTo(x + w * 0.3, y + h * 0.2, x + w * 0.42, y + h * 0.28);
|
|
4712
|
+
ctx.closePath();
|
|
4713
|
+
ctx.stroke();
|
|
4714
|
+
ctx.beginPath();
|
|
4715
|
+
ctx.arc(x, y + h * 0.42, Math.max(1, w * 0.1), 0, Math.PI * 2);
|
|
4716
|
+
ctx.fill();
|
|
4717
|
+
ctx.restore();
|
|
4718
|
+
};
|
|
4719
|
+
const drawAlertLine = (alert, yFromPrice, chartLeft, chartTop, chartRight, chartBottom) => {
|
|
4720
|
+
const lineY = getLineY(alert.price, yFromPrice, chartTop, chartBottom, false);
|
|
4721
|
+
if (lineY === null) return;
|
|
4722
|
+
const dim = alert.triggered || !alert.active;
|
|
4723
|
+
const color = alert.color || "#f7a600";
|
|
4724
|
+
const axis = resolvedAxis;
|
|
4725
|
+
ctx.save();
|
|
4726
|
+
ctx.globalAlpha = dim ? 0.5 : 1;
|
|
4727
|
+
ctx.strokeStyle = color;
|
|
4728
|
+
ctx.lineWidth = 1;
|
|
4729
|
+
applyDashPattern("dashed", dashPatterns.dotted, dashPatterns.dashed);
|
|
4730
|
+
ctx.beginPath();
|
|
4731
|
+
ctx.moveTo(crisp(chartLeft), crisp(lineY));
|
|
4732
|
+
ctx.lineTo(crisp(chartRight), crisp(lineY));
|
|
4733
|
+
ctx.stroke();
|
|
4734
|
+
ctx.restore();
|
|
4735
|
+
const fontSize = Math.max(8, axis.fontSize);
|
|
4736
|
+
ctx.font = `${fontSize}px ${mergedOptions.fontFamily}`;
|
|
4737
|
+
const text = alert.label ? `${formatPrice(alert.price)} ${alert.label}` : formatPrice(alert.price);
|
|
4738
|
+
const paddingX = 6;
|
|
4739
|
+
const bellWidth = fontSize + 2;
|
|
4740
|
+
const labelHeight = 20;
|
|
4741
|
+
const hovered = hoveredAlertId === alert.id;
|
|
4742
|
+
const removeWidth = hovered ? labelHeight : 0;
|
|
4743
|
+
const contentWidth = Math.ceil(measureTextWidth(text)) + bellWidth + paddingX * 3 + removeWidth;
|
|
4744
|
+
const labelWidth = getRightAxisLabelWidth(chartRight, contentWidth);
|
|
4745
|
+
const labelX = getRightAxisLabelX(chartRight);
|
|
4746
|
+
const labelY = placeRightAxisLabel(lineY - labelHeight / 2, labelHeight, chartTop, chartBottom - labelHeight);
|
|
4747
|
+
ctx.save();
|
|
4748
|
+
ctx.globalAlpha = dim ? 0.6 : 1;
|
|
4749
|
+
ctx.fillStyle = color;
|
|
4750
|
+
fillRoundedRect(Math.round(labelX), Math.round(labelY), labelWidth, labelHeight, 3);
|
|
4751
|
+
const inkColor = labelTextColorOn(color, "#101114");
|
|
4752
|
+
drawBellGlyph(labelX + paddingX + bellWidth / 2, labelY + labelHeight / 2, fontSize, inkColor);
|
|
4753
|
+
drawText(text, labelX + paddingX * 2 + bellWidth, labelY + labelHeight / 2, "left", "middle", inkColor);
|
|
4754
|
+
if (hovered) {
|
|
4755
|
+
const removeX = labelX + labelWidth - removeWidth;
|
|
4756
|
+
ctx.strokeStyle = inkColor;
|
|
4757
|
+
ctx.lineWidth = 1.4;
|
|
4758
|
+
const inset = 6;
|
|
4759
|
+
ctx.beginPath();
|
|
4760
|
+
ctx.moveTo(removeX + inset, labelY + inset);
|
|
4761
|
+
ctx.lineTo(removeX + removeWidth - inset, labelY + labelHeight - inset);
|
|
4762
|
+
ctx.moveTo(removeX + removeWidth - inset, labelY + inset);
|
|
4763
|
+
ctx.lineTo(removeX + inset, labelY + labelHeight - inset);
|
|
4764
|
+
ctx.stroke();
|
|
4765
|
+
alertRegions.push({
|
|
4766
|
+
id: alert.id,
|
|
4767
|
+
x: removeX,
|
|
4768
|
+
y: labelY,
|
|
4769
|
+
width: removeWidth,
|
|
4770
|
+
height: labelHeight,
|
|
4771
|
+
part: "remove"
|
|
4772
|
+
});
|
|
4773
|
+
}
|
|
4774
|
+
ctx.restore();
|
|
4775
|
+
alertRegions.push({ id: alert.id, x: labelX, y: labelY, width: labelWidth - removeWidth, height: labelHeight, part: "line" });
|
|
4776
|
+
alertRegions.push({ id: alert.id, x: chartLeft, y: lineY - 5, width: chartRight - chartLeft, height: 10, part: "line" });
|
|
4777
|
+
};
|
|
4272
4778
|
const drawOrderLine = (line, yFromPrice, chartLeft, chartTop, chartRight, chartBottom) => {
|
|
4273
4779
|
const mergedLine = { ...DEFAULT_ORDER_LINE_OPTIONS, ...line };
|
|
4274
4780
|
const renderPrice = mergedLine.behavior === "follow" && Number.isFinite(mergedLine.followPrice) ? mergedLine.followPrice : mergedLine.price;
|
|
@@ -4553,6 +5059,37 @@ function createChart(element, options = {}) {
|
|
|
4553
5059
|
ctx.closePath();
|
|
4554
5060
|
ctx.stroke();
|
|
4555
5061
|
};
|
|
5062
|
+
const drawMarkBadge = (x, y, radius, color, shape, label, textColor) => {
|
|
5063
|
+
ctx.save();
|
|
5064
|
+
ctx.fillStyle = color;
|
|
5065
|
+
ctx.beginPath();
|
|
5066
|
+
if (shape === "square") {
|
|
5067
|
+
ctx.rect(x - radius, y - radius, radius * 2, radius * 2);
|
|
5068
|
+
} else if (shape === "diamond") {
|
|
5069
|
+
ctx.moveTo(x, y - radius);
|
|
5070
|
+
ctx.lineTo(x + radius, y);
|
|
5071
|
+
ctx.lineTo(x, y + radius);
|
|
5072
|
+
ctx.lineTo(x - radius, y);
|
|
5073
|
+
ctx.closePath();
|
|
5074
|
+
} else if (shape === "flag") {
|
|
5075
|
+
ctx.rect(x - radius * 0.16, y - radius, radius * 0.32, radius * 2);
|
|
5076
|
+
ctx.moveTo(x + radius * 0.16, y - radius);
|
|
5077
|
+
ctx.lineTo(x + radius * 1.35, y - radius * 0.45);
|
|
5078
|
+
ctx.lineTo(x + radius * 0.16, y + radius * 0.1);
|
|
5079
|
+
ctx.closePath();
|
|
5080
|
+
} else {
|
|
5081
|
+
ctx.arc(x, y, radius, 0, Math.PI * 2);
|
|
5082
|
+
}
|
|
5083
|
+
ctx.fill();
|
|
5084
|
+
if (label && shape !== "flag") {
|
|
5085
|
+
ctx.fillStyle = textColor;
|
|
5086
|
+
ctx.font = `600 ${Math.max(8, Math.round(radius * 1.2))}px ${mergedOptions.fontFamily}`;
|
|
5087
|
+
ctx.textAlign = "center";
|
|
5088
|
+
ctx.textBaseline = "middle";
|
|
5089
|
+
ctx.fillText(label.slice(0, 2), x, y + 0.5);
|
|
5090
|
+
}
|
|
5091
|
+
ctx.restore();
|
|
5092
|
+
};
|
|
4556
5093
|
const setCrosshairPoint = (point) => {
|
|
4557
5094
|
const current = crosshairPoint;
|
|
4558
5095
|
if (point === null && current === null) {
|
|
@@ -4600,6 +5137,8 @@ function createChart(element, options = {}) {
|
|
|
4600
5137
|
const shouldUpdateAutoScale = options2.updateAutoScale ?? true;
|
|
4601
5138
|
orderActionRegions = [];
|
|
4602
5139
|
orderDragRegions = [];
|
|
5140
|
+
alertRegions = [];
|
|
5141
|
+
markRegions = [];
|
|
4603
5142
|
crosshairPriceActionRegion = null;
|
|
4604
5143
|
const pixelRatio = getPixelRatio();
|
|
4605
5144
|
canvas.style.width = `${width}px`;
|
|
@@ -4682,7 +5221,23 @@ function createChart(element, options = {}) {
|
|
|
4682
5221
|
}
|
|
4683
5222
|
const maxRightMargin = Math.max(margin.right, width - margin.left - 160);
|
|
4684
5223
|
const nextRightMargin = Math.min(maxRightMargin, Math.max(margin.right, Math.ceil(required + 1)));
|
|
4685
|
-
|
|
5224
|
+
if (nextRightMargin >= cachedRightMargin) {
|
|
5225
|
+
cachedRightMargin = nextRightMargin;
|
|
5226
|
+
rightMarginShrinkPendingSince = null;
|
|
5227
|
+
} else if (cachedRightMargin - nextRightMargin > RIGHT_MARGIN_SHRINK_EPSILON_PX) {
|
|
5228
|
+
const now = performance.now();
|
|
5229
|
+
if (rightMarginShrinkPendingSince === null) {
|
|
5230
|
+
rightMarginShrinkPendingSince = now;
|
|
5231
|
+
}
|
|
5232
|
+
if (now - rightMarginShrinkPendingSince >= RIGHT_MARGIN_SHRINK_DELAY_MS) {
|
|
5233
|
+
cachedRightMargin = nextRightMargin;
|
|
5234
|
+
rightMarginShrinkPendingSince = null;
|
|
5235
|
+
} else {
|
|
5236
|
+
scheduleDraw({ updateAutoScale: false });
|
|
5237
|
+
}
|
|
5238
|
+
} else {
|
|
5239
|
+
rightMarginShrinkPendingSince = null;
|
|
5240
|
+
}
|
|
4686
5241
|
return Math.min(maxRightMargin, cachedRightMargin);
|
|
4687
5242
|
};
|
|
4688
5243
|
const rightMargin = estimateRightMargin();
|
|
@@ -5855,12 +6410,14 @@ function createChart(element, options = {}) {
|
|
|
5855
6410
|
if (!prevTime) {
|
|
5856
6411
|
weight = 7;
|
|
5857
6412
|
} else {
|
|
5858
|
-
|
|
5859
|
-
|
|
5860
|
-
|
|
6413
|
+
const prev = zoneClock.parts(prevTime.getTime());
|
|
6414
|
+
const current = zoneClock.parts(time.getTime());
|
|
6415
|
+
if (prev.year !== current.year) weight = 9;
|
|
6416
|
+
else if (prev.month !== current.month) weight = 8;
|
|
6417
|
+
else if (prev.day !== current.day) weight = 7;
|
|
5861
6418
|
else {
|
|
5862
|
-
const prevMinutes =
|
|
5863
|
-
const curMinutes =
|
|
6419
|
+
const prevMinutes = prev.hour * 60 + prev.minute;
|
|
6420
|
+
const curMinutes = current.hour * 60 + current.minute;
|
|
5864
6421
|
const crossed = (span) => Math.floor(prevMinutes / span) !== Math.floor(curMinutes / span);
|
|
5865
6422
|
if (crossed(360)) weight = 6;
|
|
5866
6423
|
else if (crossed(60)) weight = 5;
|
|
@@ -5906,31 +6463,64 @@ function createChart(element, options = {}) {
|
|
|
5906
6463
|
ctx.stroke();
|
|
5907
6464
|
ctx.restore();
|
|
5908
6465
|
}
|
|
5909
|
-
|
|
6466
|
+
const sessionSeparatorsOn = grid.sessionSeparators || compiledSession !== null && sessionOptions.separators !== false;
|
|
6467
|
+
const intradaySeries = (() => {
|
|
6468
|
+
if (data.length < 2) return false;
|
|
6469
|
+
const probe = Math.min(Math.max(1, startIndex), data.length - 1);
|
|
6470
|
+
const delta = Math.abs(data[probe].time.getTime() - data[probe - 1].time.getTime());
|
|
6471
|
+
return delta > 0 && delta < 0.9 * 864e5;
|
|
6472
|
+
})();
|
|
6473
|
+
if (compiledSession && sessionOptions.highlightOutOfSession && intradaySeries && data.length > 0) {
|
|
6474
|
+
const session = compiledSession;
|
|
6475
|
+
ctx.save();
|
|
6476
|
+
ctx.fillStyle = sessionOptions.outOfSessionColor;
|
|
6477
|
+
let runStart = null;
|
|
6478
|
+
const flushRun = (endExclusive) => {
|
|
6479
|
+
if (runStart === null) return;
|
|
6480
|
+
const left = chartLeft + (runStart - xStart) / xSpan * chartWidth;
|
|
6481
|
+
const right = chartLeft + (endExclusive - xStart) / xSpan * chartWidth;
|
|
6482
|
+
const clampedLeft = Math.max(chartLeft, left);
|
|
6483
|
+
const clampedRight = Math.min(chartRight, right);
|
|
6484
|
+
if (clampedRight > clampedLeft) {
|
|
6485
|
+
ctx.fillRect(clampedLeft, chartTop, clampedRight - clampedLeft, fullChartBottom - chartTop);
|
|
6486
|
+
}
|
|
6487
|
+
runStart = null;
|
|
6488
|
+
};
|
|
6489
|
+
for (let index = Math.max(0, startIndex); index <= endIndex; index += 1) {
|
|
6490
|
+
const point = data[index];
|
|
6491
|
+
if (!point) continue;
|
|
6492
|
+
const inSession = sessionInfoAt(session, point.time.getTime()).inSession;
|
|
6493
|
+
if (!inSession) {
|
|
6494
|
+
if (runStart === null) runStart = index;
|
|
6495
|
+
} else {
|
|
6496
|
+
flushRun(index);
|
|
6497
|
+
}
|
|
6498
|
+
}
|
|
6499
|
+
flushRun(endIndex + 1);
|
|
6500
|
+
ctx.restore();
|
|
6501
|
+
}
|
|
6502
|
+
if (sessionSeparatorsOn && data.length > 1 && intradaySeries) {
|
|
5910
6503
|
const first = Math.max(1, startIndex);
|
|
5911
|
-
const
|
|
5912
|
-
|
|
5913
|
-
|
|
5914
|
-
|
|
5915
|
-
|
|
5916
|
-
|
|
5917
|
-
|
|
5918
|
-
|
|
5919
|
-
|
|
5920
|
-
|
|
5921
|
-
|
|
5922
|
-
|
|
5923
|
-
if (sessionDayOf(point.time.getTime()) === sessionDayOf(prev.time.getTime())) {
|
|
5924
|
-
continue;
|
|
5925
|
-
}
|
|
5926
|
-
const x = chartLeft + (index - xStart) / xSpan * chartWidth;
|
|
5927
|
-
ctx.beginPath();
|
|
5928
|
-
ctx.moveTo(crisp(x), crisp(chartTop));
|
|
5929
|
-
ctx.lineTo(crisp(x), crisp(fullChartBottom));
|
|
5930
|
-
ctx.stroke();
|
|
6504
|
+
const session = compiledSession;
|
|
6505
|
+
const dayOf = (ms) => session ? sessionInfoAt(session, ms).sessionDay : zoneClock.dayKey(ms);
|
|
6506
|
+
ctx.save();
|
|
6507
|
+
ctx.globalAlpha = clamp(grid.sessionSeparatorOpacity, 0, 1);
|
|
6508
|
+
ctx.strokeStyle = grid.sessionSeparatorColor;
|
|
6509
|
+
ctx.lineWidth = 1;
|
|
6510
|
+
for (let index = first; index <= endIndex; index += 1) {
|
|
6511
|
+
const point = data[index];
|
|
6512
|
+
const prev = data[index - 1];
|
|
6513
|
+
if (!point || !prev) continue;
|
|
6514
|
+
if (dayOf(point.time.getTime()) === dayOf(prev.time.getTime())) {
|
|
6515
|
+
continue;
|
|
5931
6516
|
}
|
|
5932
|
-
|
|
6517
|
+
const x = chartLeft + (index - xStart) / xSpan * chartWidth;
|
|
6518
|
+
ctx.beginPath();
|
|
6519
|
+
ctx.moveTo(crisp(x), crisp(chartTop));
|
|
6520
|
+
ctx.lineTo(crisp(x), crisp(fullChartBottom));
|
|
6521
|
+
ctx.stroke();
|
|
5933
6522
|
}
|
|
6523
|
+
ctx.restore();
|
|
5934
6524
|
}
|
|
5935
6525
|
const tickerOpts = mergedOptions.tickerLine ?? DEFAULT_OPTIONS.tickerLine;
|
|
5936
6526
|
const useSmoothedCandle = tickerOpts.smoothing && smoothedTickerPrice !== null;
|
|
@@ -6511,6 +7101,35 @@ function createChart(element, options = {}) {
|
|
|
6511
7101
|
ctx.textAlign = prevAlign;
|
|
6512
7102
|
ctx.textBaseline = prevBaseline;
|
|
6513
7103
|
}
|
|
7104
|
+
if (barMarks.length > 0 && data.length > 0) {
|
|
7105
|
+
const prevFont = ctx.font;
|
|
7106
|
+
const prevAlign = ctx.textAlign;
|
|
7107
|
+
const prevBaseline = ctx.textBaseline;
|
|
7108
|
+
ctx.textAlign = "center";
|
|
7109
|
+
ctx.textBaseline = "middle";
|
|
7110
|
+
for (const mark of barMarks) {
|
|
7111
|
+
const ms = typeof mark.time === "number" ? mark.time : Date.parse(String(mark.time));
|
|
7112
|
+
if (!Number.isFinite(ms)) continue;
|
|
7113
|
+
const index = findNearestIndexForTimeMs(ms);
|
|
7114
|
+
if (index === null) continue;
|
|
7115
|
+
const bar = data[index];
|
|
7116
|
+
if (!bar) continue;
|
|
7117
|
+
const x = chartLeft + (index + 0.5 - xStart) / xSpan * chartWidth;
|
|
7118
|
+
if (x < chartLeft - 20 || x > chartRight + 20) continue;
|
|
7119
|
+
const radius = Math.max(4, Number(mark.size) || 7);
|
|
7120
|
+
const above = mark.position === "above";
|
|
7121
|
+
const y = Number.isFinite(mark.price) ? yFromPrice(Number(mark.price)) : above ? yFromPrice(bar.h) - radius - 8 : yFromPrice(bar.l) + radius + 8;
|
|
7122
|
+
if (y < chartTop - radius || y > chartBottom + radius) continue;
|
|
7123
|
+
const key = mark.id ?? `bar:${index}:${mark.label ?? ""}`;
|
|
7124
|
+
const hovered = hoveredMarkKey === `bar|${key}`;
|
|
7125
|
+
const color = mark.color ?? "#f7a600";
|
|
7126
|
+
drawMarkBadge(x, y, hovered ? radius + 1.5 : radius, color, mark.shape ?? "circle", mark.label ?? "", mark.textColor ?? "#101114");
|
|
7127
|
+
markRegions.push({ kind: "bar", mark, id: key, x, y, radius: radius + 3 });
|
|
7128
|
+
}
|
|
7129
|
+
ctx.font = prevFont;
|
|
7130
|
+
ctx.textAlign = prevAlign;
|
|
7131
|
+
ctx.textBaseline = prevBaseline;
|
|
7132
|
+
}
|
|
6514
7133
|
ctx.restore();
|
|
6515
7134
|
const positionAxisGutter = Math.max(0, width - chartRight);
|
|
6516
7135
|
if (positionAxisGutter > 0) {
|
|
@@ -7021,6 +7640,9 @@ function createChart(element, options = {}) {
|
|
|
7021
7640
|
for (const priceLine of priceLines) {
|
|
7022
7641
|
drawPriceLine(priceLine, yFromPrice, chartLeft, chartTop, chartRight, chartBottom);
|
|
7023
7642
|
}
|
|
7643
|
+
for (const alert of alerts) {
|
|
7644
|
+
drawAlertLine(alert, yFromPrice, chartLeft, chartTop, chartRight, chartBottom);
|
|
7645
|
+
}
|
|
7024
7646
|
for (const orderLine of orderLines) {
|
|
7025
7647
|
drawOrderLine(orderLine, yFromPrice, chartLeft, chartTop, chartRight, chartBottom);
|
|
7026
7648
|
}
|
|
@@ -7130,11 +7752,36 @@ function createChart(element, options = {}) {
|
|
|
7130
7752
|
for (const tick of timeTicks) {
|
|
7131
7753
|
const tickTime = getTimeForIndex(tick.index);
|
|
7132
7754
|
if (!tickTime) continue;
|
|
7133
|
-
const
|
|
7755
|
+
const tickMs = tickTime.getTime();
|
|
7756
|
+
const timeLabel = tick.weight >= 9 ? zoneClock.formatYear(tickMs) : tick.weight === 8 ? zoneClock.formatMonth(tickMs) : tick.weight === 7 ? zoneClock.formatDayMonth(tickMs) : formatClock(tickTime);
|
|
7134
7757
|
drawText(timeLabel, tick.x, (fullChartBottom + height) / 2, "center", "middle", xAxis.textColor);
|
|
7135
7758
|
}
|
|
7136
7759
|
ctx.font = prevFont;
|
|
7137
7760
|
}
|
|
7761
|
+
if (timescaleMarks.length > 0 && data.length > 0) {
|
|
7762
|
+
const radius = 6;
|
|
7763
|
+
const markY = fullChartBottom - radius - 2;
|
|
7764
|
+
for (const mark of timescaleMarks) {
|
|
7765
|
+
const ms = typeof mark.time === "number" ? mark.time : Date.parse(String(mark.time));
|
|
7766
|
+
if (!Number.isFinite(ms)) continue;
|
|
7767
|
+
const index = findNearestIndexForTimeMs(ms);
|
|
7768
|
+
if (index === null) continue;
|
|
7769
|
+
const x = chartLeft + (index + 0.5 - xStart) / xSpan * chartWidth;
|
|
7770
|
+
if (x < chartLeft - 10 || x > chartRight + 10) continue;
|
|
7771
|
+
const key = mark.id ?? `ts:${index}:${mark.label ?? ""}`;
|
|
7772
|
+
const hovered = hoveredMarkKey === `timescale|${key}`;
|
|
7773
|
+
drawMarkBadge(
|
|
7774
|
+
x,
|
|
7775
|
+
markY,
|
|
7776
|
+
hovered ? radius + 1.5 : radius,
|
|
7777
|
+
mark.color ?? "#5b8def",
|
|
7778
|
+
mark.shape ?? "circle",
|
|
7779
|
+
mark.label ?? "",
|
|
7780
|
+
mark.textColor ?? "#0b1220"
|
|
7781
|
+
);
|
|
7782
|
+
markRegions.push({ kind: "timescale", mark, id: key, x, y: markY, radius: radius + 3 });
|
|
7783
|
+
}
|
|
7784
|
+
}
|
|
7138
7785
|
if (labels.visible && labels.showCountdownToBarClose && lastPoint) {
|
|
7139
7786
|
const stepMs = getTimeStepMs();
|
|
7140
7787
|
const nowMs = Date.now() + (Number(mergedOptions.clockOffsetMs) || 0);
|
|
@@ -7760,6 +8407,22 @@ function createChart(element, options = {}) {
|
|
|
7760
8407
|
(region) => x >= region.x && x <= region.x + region.width && y >= region.y && y <= region.y + region.height
|
|
7761
8408
|
);
|
|
7762
8409
|
};
|
|
8410
|
+
const getAlertRegionAt = (x, y) => {
|
|
8411
|
+
const hits = alertRegions.filter(
|
|
8412
|
+
(region) => x >= region.x && x <= region.x + region.width && y >= region.y && y <= region.y + region.height
|
|
8413
|
+
);
|
|
8414
|
+
if (hits.length === 0) return null;
|
|
8415
|
+
return hits.find((region) => region.part === "remove") ?? hits[0];
|
|
8416
|
+
};
|
|
8417
|
+
const getMarkAt = (x, y) => {
|
|
8418
|
+
const tolerance = touchInputActive ? Math.max(1, mergedOptions.touch?.hitToleranceScale ?? 2.2) : 1;
|
|
8419
|
+
for (let index = markRegions.length - 1; index >= 0; index -= 1) {
|
|
8420
|
+
const region = markRegions[index];
|
|
8421
|
+
const reach = region.radius * tolerance;
|
|
8422
|
+
if (Math.abs(x - region.x) <= reach && Math.abs(y - region.y) <= reach) return region;
|
|
8423
|
+
}
|
|
8424
|
+
return null;
|
|
8425
|
+
};
|
|
7763
8426
|
const getCrosshairPriceActionRegion = (x, y) => {
|
|
7764
8427
|
if (!crosshairPriceActionRegion) {
|
|
7765
8428
|
return null;
|
|
@@ -8919,7 +9582,10 @@ function createChart(element, options = {}) {
|
|
|
8919
9582
|
pointerDownInfo = null;
|
|
8920
9583
|
orderDragState = null;
|
|
8921
9584
|
actionDragState = null;
|
|
8922
|
-
drawingDragState
|
|
9585
|
+
if (drawingDragState) {
|
|
9586
|
+
drawingDragState = null;
|
|
9587
|
+
commitHistory();
|
|
9588
|
+
}
|
|
8923
9589
|
canvas.style.cursor = "default";
|
|
8924
9590
|
setCrosshairPoint(null);
|
|
8925
9591
|
};
|
|
@@ -9014,6 +9680,20 @@ function createChart(element, options = {}) {
|
|
|
9014
9680
|
});
|
|
9015
9681
|
return;
|
|
9016
9682
|
}
|
|
9683
|
+
const markHit = getMarkAt(point.x, point.y);
|
|
9684
|
+
if (markHit && markClickHandler) {
|
|
9685
|
+
const rect = canvas.getBoundingClientRect();
|
|
9686
|
+
setCrosshairPoint(null);
|
|
9687
|
+
markClickHandler({
|
|
9688
|
+
kind: markHit.kind,
|
|
9689
|
+
mark: markHit.mark,
|
|
9690
|
+
x: markHit.x,
|
|
9691
|
+
y: markHit.y,
|
|
9692
|
+
clientX: rect.left + markHit.x,
|
|
9693
|
+
clientY: rect.top + markHit.y
|
|
9694
|
+
});
|
|
9695
|
+
return;
|
|
9696
|
+
}
|
|
9017
9697
|
const orderRegion = getOrderActionRegion(point.x, point.y);
|
|
9018
9698
|
if (orderRegion) {
|
|
9019
9699
|
if (orderRegion.draggable) {
|
|
@@ -9052,6 +9732,28 @@ function createChart(element, options = {}) {
|
|
|
9052
9732
|
setCrosshairPoint(null);
|
|
9053
9733
|
return;
|
|
9054
9734
|
}
|
|
9735
|
+
const alertHit = getAlertRegionAt(point.x, point.y);
|
|
9736
|
+
if (alertHit) {
|
|
9737
|
+
const target = alerts.find((alert) => alert.id === alertHit.id);
|
|
9738
|
+
if (target) {
|
|
9739
|
+
setCrosshairPoint(null);
|
|
9740
|
+
if (alertHit.part === "remove") {
|
|
9741
|
+
const removed = serializeAlert(target);
|
|
9742
|
+
removeAlert(target.id);
|
|
9743
|
+
alertActionHandler?.({ alert: removed, action: "remove" });
|
|
9744
|
+
return;
|
|
9745
|
+
}
|
|
9746
|
+
if (target.draggable) {
|
|
9747
|
+
activePointerId = event.pointerId;
|
|
9748
|
+
alertDragState = { id: target.id, startPrice: target.price, lastPrice: target.price, moved: false };
|
|
9749
|
+
capturePointer(event.pointerId);
|
|
9750
|
+
canvas.style.cursor = "ns-resize";
|
|
9751
|
+
return;
|
|
9752
|
+
}
|
|
9753
|
+
alertActionHandler?.({ alert: serializeAlert(target), action: "click", price: target.price });
|
|
9754
|
+
return;
|
|
9755
|
+
}
|
|
9756
|
+
}
|
|
9055
9757
|
const paneButtonHit = getPaneButtonHit(point.x, point.y);
|
|
9056
9758
|
if (paneButtonHit) {
|
|
9057
9759
|
setCrosshairPoint(null);
|
|
@@ -9294,6 +9996,24 @@ function createChart(element, options = {}) {
|
|
|
9294
9996
|
setCrosshairPoint(null);
|
|
9295
9997
|
return;
|
|
9296
9998
|
}
|
|
9999
|
+
if (alertDragState) {
|
|
10000
|
+
if (activePointerId !== null && event.pointerId !== activePointerId) {
|
|
10001
|
+
return;
|
|
10002
|
+
}
|
|
10003
|
+
const nextPrice = roundToPricePrecision(priceFromCanvasY(point.y));
|
|
10004
|
+
if (nextPrice !== alertDragState.lastPrice) {
|
|
10005
|
+
alertDragState.lastPrice = nextPrice;
|
|
10006
|
+
alertDragState.moved = true;
|
|
10007
|
+
const target = alerts.find((alert) => alert.id === alertDragState?.id);
|
|
10008
|
+
if (target) {
|
|
10009
|
+
target.price = nextPrice;
|
|
10010
|
+
target.lastPrice = data[data.length - 1]?.c ?? null;
|
|
10011
|
+
alertActionHandler?.({ alert: serializeAlert(target), action: "move", price: nextPrice, dragging: true });
|
|
10012
|
+
}
|
|
10013
|
+
scheduleDraw();
|
|
10014
|
+
}
|
|
10015
|
+
return;
|
|
10016
|
+
}
|
|
9297
10017
|
if (orderDragState) {
|
|
9298
10018
|
if (activePointerId !== null && event.pointerId !== activePointerId) {
|
|
9299
10019
|
return;
|
|
@@ -9360,12 +10080,49 @@ function createChart(element, options = {}) {
|
|
|
9360
10080
|
setCrosshairPoint(null);
|
|
9361
10081
|
return;
|
|
9362
10082
|
}
|
|
10083
|
+
const markHover = getMarkAt(point.x, point.y);
|
|
10084
|
+
const nextMarkKey = markHover ? `${markHover.kind}|${markHover.id}` : null;
|
|
10085
|
+
if (nextMarkKey !== hoveredMarkKey) {
|
|
10086
|
+
hoveredMarkKey = nextMarkKey;
|
|
10087
|
+
if (markHover) {
|
|
10088
|
+
const rect = canvas.getBoundingClientRect();
|
|
10089
|
+
markHoverHandler?.({
|
|
10090
|
+
kind: markHover.kind,
|
|
10091
|
+
mark: markHover.mark,
|
|
10092
|
+
x: markHover.x,
|
|
10093
|
+
y: markHover.y,
|
|
10094
|
+
clientX: rect.left + markHover.x,
|
|
10095
|
+
clientY: rect.top + markHover.y
|
|
10096
|
+
});
|
|
10097
|
+
} else {
|
|
10098
|
+
markHoverHandler?.(null);
|
|
10099
|
+
}
|
|
10100
|
+
scheduleDraw();
|
|
10101
|
+
}
|
|
10102
|
+
if (markHover) {
|
|
10103
|
+
canvas.title = markHover.mark.text ?? markHover.mark.label ?? "";
|
|
10104
|
+
canvas.style.cursor = "pointer";
|
|
10105
|
+
setCrosshairPoint(null);
|
|
10106
|
+
return;
|
|
10107
|
+
}
|
|
9363
10108
|
const orderDragRegion = getOrderDragRegion(point.x, point.y);
|
|
9364
10109
|
if (orderDragRegion) {
|
|
9365
10110
|
canvas.style.cursor = "ns-resize";
|
|
9366
10111
|
setCrosshairPoint(null);
|
|
9367
10112
|
return;
|
|
9368
10113
|
}
|
|
10114
|
+
const alertHover = getAlertRegionAt(point.x, point.y);
|
|
10115
|
+
const nextAlertId = alertHover?.id ?? null;
|
|
10116
|
+
if (nextAlertId !== hoveredAlertId) {
|
|
10117
|
+
hoveredAlertId = nextAlertId;
|
|
10118
|
+
scheduleDraw();
|
|
10119
|
+
}
|
|
10120
|
+
if (alertHover) {
|
|
10121
|
+
canvas.title = alertHover.part === "remove" ? "Remove alert" : "Drag to move alert";
|
|
10122
|
+
canvas.style.cursor = alertHover.part === "remove" ? "pointer" : "ns-resize";
|
|
10123
|
+
setCrosshairPoint(null);
|
|
10124
|
+
return;
|
|
10125
|
+
}
|
|
9369
10126
|
const hoveredPane = getPaneAt(point.x, point.y);
|
|
9370
10127
|
const nextHoveredPaneId = hoveredPane?.id ?? null;
|
|
9371
10128
|
if (nextHoveredPaneId !== hoveredPaneId) {
|
|
@@ -9523,6 +10280,21 @@ function createChart(element, options = {}) {
|
|
|
9523
10280
|
scheduleDraw();
|
|
9524
10281
|
return;
|
|
9525
10282
|
}
|
|
10283
|
+
if (alertDragState) {
|
|
10284
|
+
const target = alerts.find((alert) => alert.id === alertDragState?.id);
|
|
10285
|
+
if (target && alertDragState.moved) {
|
|
10286
|
+
alertActionHandler?.({
|
|
10287
|
+
alert: serializeAlert(target),
|
|
10288
|
+
action: "move",
|
|
10289
|
+
price: alertDragState.lastPrice,
|
|
10290
|
+
dragging: false
|
|
10291
|
+
});
|
|
10292
|
+
} else if (target) {
|
|
10293
|
+
alertActionHandler?.({ alert: serializeAlert(target), action: "click", price: target.price });
|
|
10294
|
+
}
|
|
10295
|
+
alertDragState = null;
|
|
10296
|
+
canvas.style.cursor = "default";
|
|
10297
|
+
}
|
|
9526
10298
|
if (orderDragState) {
|
|
9527
10299
|
const moved = orderDragState.lastPrice !== orderDragState.startPrice;
|
|
9528
10300
|
const finalLine = orderLines.find((line) => line.id === orderDragState?.orderId);
|
|
@@ -9569,9 +10341,14 @@ function createChart(element, options = {}) {
|
|
|
9569
10341
|
});
|
|
9570
10342
|
paneDividerDrag = null;
|
|
9571
10343
|
}
|
|
9572
|
-
if (event?.type === "pointerleave" && (hoveredPaneId !== null || hoveredPaneButton !== null)) {
|
|
10344
|
+
if (event?.type === "pointerleave" && (hoveredPaneId !== null || hoveredPaneButton !== null || hoveredAlertId !== null || hoveredMarkKey !== null)) {
|
|
9573
10345
|
hoveredPaneId = null;
|
|
9574
10346
|
hoveredPaneButton = null;
|
|
10347
|
+
hoveredAlertId = null;
|
|
10348
|
+
if (hoveredMarkKey !== null) {
|
|
10349
|
+
hoveredMarkKey = null;
|
|
10350
|
+
markHoverHandler?.(null);
|
|
10351
|
+
}
|
|
9575
10352
|
if (canvas.title) {
|
|
9576
10353
|
canvas.title = "";
|
|
9577
10354
|
}
|
|
@@ -9747,9 +10524,6 @@ function createChart(element, options = {}) {
|
|
|
9747
10524
|
if (keyboard.enabled === false) {
|
|
9748
10525
|
return;
|
|
9749
10526
|
}
|
|
9750
|
-
if (event.altKey || event.ctrlKey || event.metaKey) {
|
|
9751
|
-
return;
|
|
9752
|
-
}
|
|
9753
10527
|
const panStep = Math.max(1, keyboard.panBars ?? 1) * (event.shiftKey ? 10 : 1);
|
|
9754
10528
|
const emit = (action, drawing) => {
|
|
9755
10529
|
keyboardShortcutHandler?.({
|
|
@@ -9759,6 +10533,21 @@ function createChart(element, options = {}) {
|
|
|
9759
10533
|
...drawing ? { drawing } : {}
|
|
9760
10534
|
});
|
|
9761
10535
|
};
|
|
10536
|
+
if ((event.ctrlKey || event.metaKey) && !event.altKey) {
|
|
10537
|
+
const key = event.key.toLowerCase();
|
|
10538
|
+
if (keyboard.undoRedo !== false && (key === "z" || key === "y")) {
|
|
10539
|
+
const wantsRedo = key === "y" || event.shiftKey;
|
|
10540
|
+
const applied = wantsRedo ? redo() : undo();
|
|
10541
|
+
if (applied) {
|
|
10542
|
+
event.preventDefault();
|
|
10543
|
+
emit(wantsRedo ? "redo" : "undo");
|
|
10544
|
+
}
|
|
10545
|
+
return;
|
|
10546
|
+
}
|
|
10547
|
+
}
|
|
10548
|
+
if (event.altKey || event.ctrlKey || event.metaKey) {
|
|
10549
|
+
return;
|
|
10550
|
+
}
|
|
9762
10551
|
switch (event.key) {
|
|
9763
10552
|
case "Delete":
|
|
9764
10553
|
case "Backspace": {
|
|
@@ -9904,6 +10693,20 @@ function createChart(element, options = {}) {
|
|
|
9904
10693
|
doubleClickEnabled = mergedOptions.doubleClickEnabled;
|
|
9905
10694
|
doubleClickAction = mergedOptions.doubleClickAction;
|
|
9906
10695
|
setIndicatorLiveThrottleMs(mergedOptions.indicatorUpdate?.liveThrottleMs ?? 80);
|
|
10696
|
+
if (nextOptions.timezone !== void 0) {
|
|
10697
|
+
timezoneSetting = nextOptions.timezone || "local";
|
|
10698
|
+
zoneClock = createZoneClock(timezoneSetting);
|
|
10699
|
+
}
|
|
10700
|
+
if (nextOptions.timeFormat !== void 0) {
|
|
10701
|
+
timeFormat = nextOptions.timeFormat === "12h" ? "12h" : "24h";
|
|
10702
|
+
}
|
|
10703
|
+
if (nextOptions.session !== void 0) {
|
|
10704
|
+
sessionOptions = { ...sessionOptions, ...nextOptions.session ?? {} };
|
|
10705
|
+
compiledSession = compileSession(sessionOptions.spec);
|
|
10706
|
+
}
|
|
10707
|
+
if (nextOptions.alerts !== void 0) {
|
|
10708
|
+
setAlerts(nextOptions.alerts ?? []);
|
|
10709
|
+
}
|
|
9907
10710
|
applyAccessibilityAttributes();
|
|
9908
10711
|
const isTickerSmoothingEnabled = mergedOptions.tickerLine?.smoothing ?? false;
|
|
9909
10712
|
if (!isTickerSmoothingEnabled) {
|
|
@@ -9964,7 +10767,7 @@ function createChart(element, options = {}) {
|
|
|
9964
10767
|
resetRightMarginCache();
|
|
9965
10768
|
scheduleDraw();
|
|
9966
10769
|
};
|
|
9967
|
-
const
|
|
10770
|
+
const setDataInternal = (nextData) => {
|
|
9968
10771
|
const hadData = data.length > 0;
|
|
9969
10772
|
const previousCount = data.length;
|
|
9970
10773
|
const previousCenterRounded = hadData ? Math.round(xCenter) : 0;
|
|
@@ -10006,9 +10809,10 @@ function createChart(element, options = {}) {
|
|
|
10006
10809
|
if (lastPoint) {
|
|
10007
10810
|
pushSmoothedTicker(lastPoint.c, lastPoint.v);
|
|
10008
10811
|
}
|
|
10812
|
+
evaluateAlertsFromLatestBar();
|
|
10009
10813
|
scheduleDraw();
|
|
10010
10814
|
};
|
|
10011
|
-
const
|
|
10815
|
+
const upsertBarInternal = (point) => {
|
|
10012
10816
|
const time = new Date(point.t);
|
|
10013
10817
|
const timeMs = time.getTime();
|
|
10014
10818
|
const open = Number(point.o);
|
|
@@ -10029,7 +10833,7 @@ function createChart(element, options = {}) {
|
|
|
10029
10833
|
};
|
|
10030
10834
|
const last = data[data.length - 1];
|
|
10031
10835
|
if (!last) {
|
|
10032
|
-
|
|
10836
|
+
setDataInternal([point]);
|
|
10033
10837
|
return;
|
|
10034
10838
|
}
|
|
10035
10839
|
const lastMs = last.time.getTime();
|
|
@@ -10051,8 +10855,163 @@ function createChart(element, options = {}) {
|
|
|
10051
10855
|
reanchorDrawingsToData();
|
|
10052
10856
|
}
|
|
10053
10857
|
pushSmoothedTicker(parsed.c, parsed.v);
|
|
10858
|
+
evaluateAlerts(parsed.c, timeMs, data.length - 1);
|
|
10054
10859
|
scheduleDraw();
|
|
10055
10860
|
};
|
|
10861
|
+
const toRawPoint = (point) => ({
|
|
10862
|
+
t: point.time.toISOString(),
|
|
10863
|
+
o: point.o,
|
|
10864
|
+
h: point.h,
|
|
10865
|
+
l: point.l,
|
|
10866
|
+
c: point.c,
|
|
10867
|
+
...point.v === void 0 ? {} : { v: point.v }
|
|
10868
|
+
});
|
|
10869
|
+
const getReplayState = () => {
|
|
10870
|
+
if (!replay) {
|
|
10871
|
+
return {
|
|
10872
|
+
active: false,
|
|
10873
|
+
playing: false,
|
|
10874
|
+
speed: 1,
|
|
10875
|
+
index: Math.max(0, data.length - 1),
|
|
10876
|
+
total: data.length,
|
|
10877
|
+
atEnd: true,
|
|
10878
|
+
timeMs: data.length > 0 ? data[data.length - 1].time.getTime() : null
|
|
10879
|
+
};
|
|
10880
|
+
}
|
|
10881
|
+
const total = replay.fullRaw.length;
|
|
10882
|
+
const revealed = replay.fullRaw[replay.cursor];
|
|
10883
|
+
const revealedMs = revealed ? Date.parse(String(revealed.t)) : Number.NaN;
|
|
10884
|
+
return {
|
|
10885
|
+
active: true,
|
|
10886
|
+
playing: replay.playing,
|
|
10887
|
+
speed: replay.speed,
|
|
10888
|
+
index: replay.cursor,
|
|
10889
|
+
total,
|
|
10890
|
+
atEnd: replay.cursor >= total - 1,
|
|
10891
|
+
timeMs: Number.isFinite(revealedMs) ? revealedMs : null
|
|
10892
|
+
};
|
|
10893
|
+
};
|
|
10894
|
+
const emitReplayState = () => {
|
|
10895
|
+
replayStateHandler?.(getReplayState());
|
|
10896
|
+
};
|
|
10897
|
+
const applyReplaySlice = () => {
|
|
10898
|
+
if (!replay) return;
|
|
10899
|
+
setDataInternal(replay.fullRaw.slice(0, replay.cursor + 1));
|
|
10900
|
+
};
|
|
10901
|
+
const clearReplayTimer = () => {
|
|
10902
|
+
if (replay?.timerId) {
|
|
10903
|
+
clearTimeout(replay.timerId);
|
|
10904
|
+
replay.timerId = null;
|
|
10905
|
+
}
|
|
10906
|
+
};
|
|
10907
|
+
const scheduleReplayTick = () => {
|
|
10908
|
+
if (!replay || !replay.playing) return;
|
|
10909
|
+
clearReplayTimer();
|
|
10910
|
+
const intervalMs = Math.max(16, 1e3 / Math.max(0.05, replay.speed));
|
|
10911
|
+
replay.timerId = setTimeout(() => {
|
|
10912
|
+
if (!replay || !replay.playing) return;
|
|
10913
|
+
if (replay.cursor >= replay.fullRaw.length - 1) {
|
|
10914
|
+
replay.playing = false;
|
|
10915
|
+
clearReplayTimer();
|
|
10916
|
+
emitReplayState();
|
|
10917
|
+
return;
|
|
10918
|
+
}
|
|
10919
|
+
replay.cursor += 1;
|
|
10920
|
+
applyReplaySlice();
|
|
10921
|
+
emitReplayState();
|
|
10922
|
+
scheduleReplayTick();
|
|
10923
|
+
}, intervalMs);
|
|
10924
|
+
};
|
|
10925
|
+
const startReplay = (startOptions = {}) => {
|
|
10926
|
+
const source = replay ? replay.fullRaw : data.map((point) => toRawPoint(point));
|
|
10927
|
+
if (source.length === 0) return;
|
|
10928
|
+
clearReplayTimer();
|
|
10929
|
+
let cursor;
|
|
10930
|
+
if (Number.isFinite(startOptions.fromTimeMs)) {
|
|
10931
|
+
const target = Number(startOptions.fromTimeMs);
|
|
10932
|
+
const resolved = findNearestIndexForTimeMs(target);
|
|
10933
|
+
cursor = resolved ?? Math.floor(source.length * 0.7);
|
|
10934
|
+
} else if (Number.isFinite(startOptions.fromIndex)) {
|
|
10935
|
+
cursor = Math.floor(Number(startOptions.fromIndex));
|
|
10936
|
+
} else {
|
|
10937
|
+
cursor = Math.floor(source.length * 0.7);
|
|
10938
|
+
}
|
|
10939
|
+
cursor = clamp(cursor, 0, source.length - 1);
|
|
10940
|
+
const speed = Math.max(0.05, Number(startOptions.speed) || replay?.speed || 1);
|
|
10941
|
+
replay = { fullRaw: source, cursor, playing: false, speed, timerId: null };
|
|
10942
|
+
applyReplaySlice();
|
|
10943
|
+
setFollowingLatest(true);
|
|
10944
|
+
if (startOptions.play) {
|
|
10945
|
+
replay.playing = true;
|
|
10946
|
+
scheduleReplayTick();
|
|
10947
|
+
}
|
|
10948
|
+
emitReplayState();
|
|
10949
|
+
};
|
|
10950
|
+
const stopReplay = () => {
|
|
10951
|
+
if (!replay) return;
|
|
10952
|
+
clearReplayTimer();
|
|
10953
|
+
const full = replay.fullRaw;
|
|
10954
|
+
replay = null;
|
|
10955
|
+
setDataInternal(full);
|
|
10956
|
+
setFollowingLatest(true);
|
|
10957
|
+
emitReplayState();
|
|
10958
|
+
};
|
|
10959
|
+
const replayStep = (bars = 1) => {
|
|
10960
|
+
if (!replay) return;
|
|
10961
|
+
const step = Math.trunc(bars) || 1;
|
|
10962
|
+
const next = clamp(replay.cursor + step, 0, replay.fullRaw.length - 1);
|
|
10963
|
+
if (next === replay.cursor) return;
|
|
10964
|
+
replay.cursor = next;
|
|
10965
|
+
applyReplaySlice();
|
|
10966
|
+
emitReplayState();
|
|
10967
|
+
};
|
|
10968
|
+
const replayPlay = (speed) => {
|
|
10969
|
+
if (!replay) return;
|
|
10970
|
+
if (Number.isFinite(speed)) replay.speed = Math.max(0.05, Number(speed));
|
|
10971
|
+
if (replay.cursor >= replay.fullRaw.length - 1) return;
|
|
10972
|
+
replay.playing = true;
|
|
10973
|
+
scheduleReplayTick();
|
|
10974
|
+
emitReplayState();
|
|
10975
|
+
};
|
|
10976
|
+
const replayPause = () => {
|
|
10977
|
+
if (!replay || !replay.playing) return;
|
|
10978
|
+
replay.playing = false;
|
|
10979
|
+
clearReplayTimer();
|
|
10980
|
+
emitReplayState();
|
|
10981
|
+
};
|
|
10982
|
+
const setReplaySpeed = (speed) => {
|
|
10983
|
+
if (!replay || !Number.isFinite(speed)) return;
|
|
10984
|
+
replay.speed = clamp(Number(speed), 0.05, 100);
|
|
10985
|
+
if (replay.playing) scheduleReplayTick();
|
|
10986
|
+
emitReplayState();
|
|
10987
|
+
};
|
|
10988
|
+
const onReplayStateChange = (handler) => {
|
|
10989
|
+
replayStateHandler = handler;
|
|
10990
|
+
};
|
|
10991
|
+
const setData = (nextData) => {
|
|
10992
|
+
if (replay) {
|
|
10993
|
+
replay.fullRaw = nextData.slice();
|
|
10994
|
+
replay.cursor = clamp(replay.cursor, 0, Math.max(0, replay.fullRaw.length - 1));
|
|
10995
|
+
applyReplaySlice();
|
|
10996
|
+
emitReplayState();
|
|
10997
|
+
return;
|
|
10998
|
+
}
|
|
10999
|
+
setDataInternal(nextData);
|
|
11000
|
+
};
|
|
11001
|
+
const upsertBar = (point) => {
|
|
11002
|
+
if (replay) {
|
|
11003
|
+
const timeMs = Date.parse(String(point.t));
|
|
11004
|
+
if (!Number.isFinite(timeMs)) return;
|
|
11005
|
+
const buffer = replay.fullRaw;
|
|
11006
|
+
const lastRaw = buffer[buffer.length - 1];
|
|
11007
|
+
const lastMs = lastRaw ? Date.parse(String(lastRaw.t)) : Number.NaN;
|
|
11008
|
+
if (Number.isFinite(lastMs) && timeMs === lastMs) buffer[buffer.length - 1] = point;
|
|
11009
|
+
else if (!Number.isFinite(lastMs) || timeMs > lastMs) buffer.push(point);
|
|
11010
|
+
emitReplayState();
|
|
11011
|
+
return;
|
|
11012
|
+
}
|
|
11013
|
+
upsertBarInternal(point);
|
|
11014
|
+
};
|
|
10056
11015
|
const setPriceLines = (lines) => {
|
|
10057
11016
|
priceLines = lines.map((line, index) => ({
|
|
10058
11017
|
...line,
|
|
@@ -10241,6 +11200,96 @@ function createChart(element, options = {}) {
|
|
|
10241
11200
|
tradeMarkers = Array.isArray(markers) ? markers.slice() : [];
|
|
10242
11201
|
scheduleDraw();
|
|
10243
11202
|
};
|
|
11203
|
+
const setBarMarks = (marks) => {
|
|
11204
|
+
barMarks = Array.isArray(marks) ? marks.slice() : [];
|
|
11205
|
+
scheduleDraw();
|
|
11206
|
+
};
|
|
11207
|
+
const getBarMarks = () => barMarks.slice();
|
|
11208
|
+
const setTimescaleMarks = (marks) => {
|
|
11209
|
+
timescaleMarks = Array.isArray(marks) ? marks.slice() : [];
|
|
11210
|
+
scheduleDraw();
|
|
11211
|
+
};
|
|
11212
|
+
const getTimescaleMarks = () => timescaleMarks.slice();
|
|
11213
|
+
const onMarkClick = (handler) => {
|
|
11214
|
+
markClickHandler = handler;
|
|
11215
|
+
};
|
|
11216
|
+
const onMarkHover = (handler) => {
|
|
11217
|
+
markHoverHandler = handler;
|
|
11218
|
+
};
|
|
11219
|
+
const setAlerts = (nextAlerts) => {
|
|
11220
|
+
alerts = (Array.isArray(nextAlerts) ? nextAlerts : []).map((alert) => normalizeAlert(alert));
|
|
11221
|
+
const last = data[data.length - 1];
|
|
11222
|
+
if (last) {
|
|
11223
|
+
for (const alert of alerts) alert.lastPrice = last.c;
|
|
11224
|
+
}
|
|
11225
|
+
resetRightMarginCache();
|
|
11226
|
+
scheduleDraw();
|
|
11227
|
+
};
|
|
11228
|
+
const addAlert = (alert) => {
|
|
11229
|
+
const next = normalizeAlert(alert);
|
|
11230
|
+
const last = data[data.length - 1];
|
|
11231
|
+
if (last) next.lastPrice = last.c;
|
|
11232
|
+
alerts.push(next);
|
|
11233
|
+
resetRightMarginCache();
|
|
11234
|
+
scheduleDraw();
|
|
11235
|
+
return next.id;
|
|
11236
|
+
};
|
|
11237
|
+
const updateAlert = (id, patch) => {
|
|
11238
|
+
const target = alerts.find((alert) => alert.id === id);
|
|
11239
|
+
if (!target) return;
|
|
11240
|
+
if (Number.isFinite(patch.price)) target.price = Number(patch.price);
|
|
11241
|
+
if (patch.condition) target.condition = patch.condition;
|
|
11242
|
+
if (patch.label !== void 0) target.label = patch.label;
|
|
11243
|
+
if (patch.color !== void 0) target.color = patch.color;
|
|
11244
|
+
if (patch.active !== void 0) target.active = patch.active;
|
|
11245
|
+
if (patch.once !== void 0) target.once = patch.once;
|
|
11246
|
+
if (patch.draggable !== void 0) target.draggable = patch.draggable;
|
|
11247
|
+
if (patch.triggered !== void 0) {
|
|
11248
|
+
target.triggered = patch.triggered;
|
|
11249
|
+
if (!patch.triggered) target.lastPrice = data[data.length - 1]?.c ?? null;
|
|
11250
|
+
}
|
|
11251
|
+
resetRightMarginCache();
|
|
11252
|
+
scheduleDraw();
|
|
11253
|
+
};
|
|
11254
|
+
const removeAlert = (id) => {
|
|
11255
|
+
alerts = alerts.filter((alert) => alert.id !== id);
|
|
11256
|
+
if (hoveredAlertId === id) hoveredAlertId = null;
|
|
11257
|
+
resetRightMarginCache();
|
|
11258
|
+
scheduleDraw();
|
|
11259
|
+
};
|
|
11260
|
+
const getAlerts = () => alerts.map((alert) => serializeAlert(alert));
|
|
11261
|
+
const onAlertTrigger = (handler) => {
|
|
11262
|
+
alertTriggerHandler = handler;
|
|
11263
|
+
};
|
|
11264
|
+
const onAlertAction = (handler) => {
|
|
11265
|
+
alertActionHandler = handler;
|
|
11266
|
+
};
|
|
11267
|
+
const setTimezone = (timezone) => {
|
|
11268
|
+
timezoneSetting = timezone || "local";
|
|
11269
|
+
zoneClock = createZoneClock(timezoneSetting);
|
|
11270
|
+
scheduleDraw();
|
|
11271
|
+
};
|
|
11272
|
+
const getTimezone = () => timezoneSetting;
|
|
11273
|
+
const setTimeFormat = (format) => {
|
|
11274
|
+
timeFormat = format === "12h" ? "12h" : "24h";
|
|
11275
|
+
resetRightMarginCache();
|
|
11276
|
+
scheduleDraw();
|
|
11277
|
+
};
|
|
11278
|
+
const getTimeFormat = () => timeFormat;
|
|
11279
|
+
const setSession = (session) => {
|
|
11280
|
+
if (session === null) {
|
|
11281
|
+
sessionOptions = { ...sessionOptions, spec: null };
|
|
11282
|
+
} else if (typeof session === "string") {
|
|
11283
|
+
sessionOptions = { ...sessionOptions, spec: session };
|
|
11284
|
+
} else if ("segments" in session || "timezone" in session || "days" in session) {
|
|
11285
|
+
sessionOptions = { ...sessionOptions, spec: session };
|
|
11286
|
+
} else {
|
|
11287
|
+
sessionOptions = { ...sessionOptions, ...session };
|
|
11288
|
+
}
|
|
11289
|
+
compiledSession = compileSession(sessionOptions.spec);
|
|
11290
|
+
scheduleDraw();
|
|
11291
|
+
};
|
|
11292
|
+
const getSession = () => ({ ...sessionOptions });
|
|
10244
11293
|
const setMagnetMode = (mode) => {
|
|
10245
11294
|
magnetMode = mode;
|
|
10246
11295
|
};
|
|
@@ -10260,7 +11309,13 @@ function createChart(element, options = {}) {
|
|
|
10260
11309
|
drawings = dedupeDrawingIds(nextDrawings.map((drawing) => normalizeDrawingState(drawing)));
|
|
10261
11310
|
draftDrawing = null;
|
|
10262
11311
|
reanchorDrawingsToData();
|
|
10263
|
-
|
|
11312
|
+
historySuspended += 1;
|
|
11313
|
+
try {
|
|
11314
|
+
emitDrawingsChange();
|
|
11315
|
+
} finally {
|
|
11316
|
+
historySuspended -= 1;
|
|
11317
|
+
}
|
|
11318
|
+
resetHistoryBaseline();
|
|
10264
11319
|
scheduleDraw();
|
|
10265
11320
|
};
|
|
10266
11321
|
const addDrawing = (drawing) => {
|
|
@@ -10345,7 +11400,10 @@ function createChart(element, options = {}) {
|
|
|
10345
11400
|
indicators: getIndicators(),
|
|
10346
11401
|
magnetMode: getMagnetMode(),
|
|
10347
11402
|
priceScale: getPriceScale(),
|
|
10348
|
-
drawingDefaults
|
|
11403
|
+
drawingDefaults,
|
|
11404
|
+
alerts: getAlerts(),
|
|
11405
|
+
timezone: getTimezone(),
|
|
11406
|
+
timeFormat: getTimeFormat()
|
|
10349
11407
|
};
|
|
10350
11408
|
};
|
|
10351
11409
|
const loadState = (state) => {
|
|
@@ -10374,9 +11432,19 @@ function createChart(element, options = {}) {
|
|
|
10374
11432
|
}
|
|
10375
11433
|
}
|
|
10376
11434
|
}
|
|
11435
|
+
if (Array.isArray(state.alerts)) {
|
|
11436
|
+
setAlerts(state.alerts);
|
|
11437
|
+
}
|
|
11438
|
+
if (typeof state.timezone === "string") {
|
|
11439
|
+
setTimezone(state.timezone);
|
|
11440
|
+
}
|
|
11441
|
+
if (state.timeFormat === "12h" || state.timeFormat === "24h") {
|
|
11442
|
+
setTimeFormat(state.timeFormat);
|
|
11443
|
+
}
|
|
10377
11444
|
if (state.viewport && typeof state.viewport === "object") {
|
|
10378
11445
|
setViewport(state.viewport);
|
|
10379
11446
|
}
|
|
11447
|
+
clearHistory();
|
|
10380
11448
|
};
|
|
10381
11449
|
const takeScreenshot = (options2 = {}) => {
|
|
10382
11450
|
return canvas.toDataURL(options2.type ?? "image/png", options2.quality);
|
|
@@ -10384,6 +11452,8 @@ function createChart(element, options = {}) {
|
|
|
10384
11452
|
const destroy = () => {
|
|
10385
11453
|
cancelViewportTween();
|
|
10386
11454
|
cancelLongPressTimer();
|
|
11455
|
+
clearReplayTimer();
|
|
11456
|
+
replay = null;
|
|
10387
11457
|
datafeedUnsubscribe?.();
|
|
10388
11458
|
datafeedUnsubscribe = null;
|
|
10389
11459
|
datafeed = null;
|
|
@@ -10417,6 +11487,10 @@ function createChart(element, options = {}) {
|
|
|
10417
11487
|
}
|
|
10418
11488
|
element.innerHTML = "";
|
|
10419
11489
|
};
|
|
11490
|
+
if (Array.isArray(options.alerts) && options.alerts.length > 0) {
|
|
11491
|
+
setAlerts(options.alerts);
|
|
11492
|
+
}
|
|
11493
|
+
resetHistoryBaseline();
|
|
10420
11494
|
draw();
|
|
10421
11495
|
armCountdownHeartbeat();
|
|
10422
11496
|
return {
|
|
@@ -10500,6 +11574,42 @@ function createChart(element, options = {}) {
|
|
|
10500
11574
|
saveState,
|
|
10501
11575
|
loadState,
|
|
10502
11576
|
takeScreenshot,
|
|
11577
|
+
setTimezone,
|
|
11578
|
+
getTimezone,
|
|
11579
|
+
setTimeFormat,
|
|
11580
|
+
getTimeFormat,
|
|
11581
|
+
setSession,
|
|
11582
|
+
getSession,
|
|
11583
|
+
undo,
|
|
11584
|
+
redo,
|
|
11585
|
+
canUndo: () => historyPast.length > 0,
|
|
11586
|
+
canRedo: () => historyFuture.length > 0,
|
|
11587
|
+
getUndoRedoState,
|
|
11588
|
+
onUndoRedoStateChange: (handler) => {
|
|
11589
|
+
undoRedoStateHandler = handler;
|
|
11590
|
+
},
|
|
11591
|
+
clearHistory,
|
|
11592
|
+
startReplay,
|
|
11593
|
+
stopReplay,
|
|
11594
|
+
replayStep,
|
|
11595
|
+
replayPlay,
|
|
11596
|
+
replayPause,
|
|
11597
|
+
setReplaySpeed,
|
|
11598
|
+
getReplayState,
|
|
11599
|
+
onReplayStateChange,
|
|
11600
|
+
setAlerts,
|
|
11601
|
+
addAlert,
|
|
11602
|
+
updateAlert,
|
|
11603
|
+
removeAlert,
|
|
11604
|
+
getAlerts,
|
|
11605
|
+
onAlertTrigger,
|
|
11606
|
+
onAlertAction,
|
|
11607
|
+
setBarMarks,
|
|
11608
|
+
getBarMarks,
|
|
11609
|
+
setTimescaleMarks,
|
|
11610
|
+
getTimescaleMarks,
|
|
11611
|
+
onMarkClick,
|
|
11612
|
+
onMarkHover,
|
|
10503
11613
|
resize,
|
|
10504
11614
|
destroy
|
|
10505
11615
|
};
|
|
@@ -10511,8 +11621,10 @@ function createChart(element, options = {}) {
|
|
|
10511
11621
|
FIB_DEFAULT_PALETTE,
|
|
10512
11622
|
POSITION_DEFAULT_COLORS,
|
|
10513
11623
|
SCRIPT_SOURCE_INPUT_VALUES,
|
|
11624
|
+
SESSION_PRESETS,
|
|
10514
11625
|
compileScriptIndicator,
|
|
10515
11626
|
createChart,
|
|
10516
11627
|
extractScriptInputs,
|
|
11628
|
+
isValidTimeZone,
|
|
10517
11629
|
validateScriptSource
|
|
10518
11630
|
});
|