-
Notifications
You must be signed in to change notification settings - Fork 0
Expand file tree
/
Copy pathgrafana-scroll-zoom.js
More file actions
136 lines (116 loc) · 4.22 KB
/
Copy pathgrafana-scroll-zoom.js
File metadata and controls
136 lines (116 loc) · 4.22 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
(function () {
'use strict';
console.log('[scroll-zoom] loaded');
var ZOOM_FACTOR = 0.15;
var DEBOUNCE_MS = 50;
var MIN_SPAN = 60000; // 1 minute
var MAX_SPAN = 31536000000; // 1 year
var DEFAULT_FROM = 'now-24h';
var DEFAULT_TO = 'now';
var debounceTimer = null;
var pendingFrom = null;
var pendingTo = null;
var lastFrom = null; // persists after applyZoom
var lastTo = null;
function parseTime(str) {
if (!str) return null;
var now = Date.now();
if (str === 'now') return now;
var m = str.match(/^now-(\d+)([smhdwMy])$/);
if (m) {
var mult = { s: 1e3, m: 6e4, h: 36e5, d: 864e5, w: 6048e5, M: 2592e6, y: 31536e6 };
return now - parseInt(m[1], 10) * (mult[m[2]] || 864e5);
}
var d = Date.parse(str);
if (!isNaN(d)) return d;
var n = Number(str);
return isNaN(n) ? null : n;
}
function getTimeRange() {
// 1. Mid-debounce pending values
if (pendingFrom !== null) {
return { from: pendingFrom, to: pendingTo };
}
// 2. URL params
var params = new URLSearchParams(window.location.search);
var from = parseTime(params.get('from'));
var to = parseTime(params.get('to'));
if (from && to) {
console.log('[scroll-zoom] time range from URL params');
return { from: from, to: to };
}
// 3. Last applied values (Grafana may have stripped URL params)
if (lastFrom !== null) {
console.log('[scroll-zoom] time range from last applied');
return { from: lastFrom, to: lastTo };
}
// 4. Dashboard default
console.log('[scroll-zoom] time range from dashboard default');
return { from: parseTime(DEFAULT_FROM), to: parseTime(DEFAULT_TO) };
}
function applyZoom() {
if (pendingFrom === null) return;
var url = new URL(window.location);
url.searchParams.set('from', pendingFrom.toString());
url.searchParams.set('to', pendingTo.toString());
lastFrom = pendingFrom;
lastTo = pendingTo;
console.log('[scroll-zoom] applying zoom: from=' + pendingFrom + ' to=' + pendingTo);
window.history.pushState({}, '', url.pathname + url.search + url.hash);
window.dispatchEvent(new PopStateEvent('popstate', { state: {} }));
pendingFrom = null;
pendingTo = null;
}
// Sync tracked state when Grafana changes the URL (x-axis pan, time picker, etc.)
function syncFromUrl() {
var params = new URLSearchParams(window.location.search);
var urlFrom = parseTime(params.get('from'));
var urlTo = parseTime(params.get('to'));
if (urlFrom && urlTo) {
if (urlFrom !== lastFrom || urlTo !== lastTo) {
console.log('[scroll-zoom] synced from URL: from=' + urlFrom + ' to=' + urlTo);
lastFrom = urlFrom;
lastTo = urlTo;
}
}
}
var origPushState = history.pushState.bind(history);
var origReplaceState = history.replaceState.bind(history);
history.pushState = function () {
origPushState.apply(history, arguments);
syncFromUrl();
};
history.replaceState = function () {
origReplaceState.apply(history, arguments);
syncFromUrl();
};
window.addEventListener('popstate', syncFromUrl);
document.addEventListener('wheel', function (e) {
var uplotEl = e.target.closest('.uplot');
if (!uplotEl) return;
e.preventDefault();
console.log('[scroll-zoom] wheel on uplot, deltaY=' + e.deltaY);
var range = getTimeRange();
var from = range.from;
var to = range.to;
// Cursor position as fraction of the plot area width
var plotArea = uplotEl.querySelector('.u-over');
var cursorFrac = 0.5;
if (plotArea) {
var rect = plotArea.getBoundingClientRect();
cursorFrac = Math.max(0, Math.min(1, (e.clientX - rect.left) / rect.width));
}
var span = to - from;
var zoomDir = e.deltaY > 0 ? 1 : -1; // scroll down = zoom out
var delta = span * ZOOM_FACTOR * zoomDir;
var newFrom = Math.round(from - delta * cursorFrac);
var newTo = Math.round(to + delta * (1 - cursorFrac));
// Clamp to min/max span
var newSpan = newTo - newFrom;
if (newSpan < MIN_SPAN || newSpan > MAX_SPAN) return;
pendingFrom = newFrom;
pendingTo = newTo;
clearTimeout(debounceTimer);
debounceTimer = setTimeout(applyZoom, DEBOUNCE_MS);
}, { passive: false });
})();