Charts: clock times and dates along the bottom
Traffic and latency charts label the x-axis with clock times every 3 hours (6 on phones, the date at midnight), or dates for the 7- and 30-day ranges. The hover readout for hourly bars shows the clock time range instead of "3 h ago".
This commit is contained in:
@@ -254,7 +254,10 @@ fieldset { border: 0; margin: 0; padding: 0; min-width: 0; display: flex; flex-d
|
|||||||
.chart .grp span.up { background: var(--up); }
|
.chart .grp span.up { background: var(--up); }
|
||||||
.chart .grp span.total { background: var(--down); }
|
.chart .grp span.total { background: var(--down); }
|
||||||
.chart .grp.on span.total { background: var(--down-strong); }
|
.chart .grp.on span.total { background: var(--down-strong); }
|
||||||
.xaxis { display: flex; justify-content: space-between; margin: 8px 0 0 56px; font-size: 11px; color: var(--axis-ink); }
|
.xaxis { position: relative; height: 22px; margin-left: 56px; font-size: 11px; color: var(--axis-ink); }
|
||||||
|
.xaxis span { position: absolute; top: 0; padding-top: 7px; transform: translateX(-50%); white-space: nowrap; font-variant-numeric: tabular-nums; }
|
||||||
|
.xaxis span.edge::before { content: ''; position: absolute; left: 50%; top: 0; height: 4px; border-left: 1px solid var(--axis); }
|
||||||
|
@media (max-width: 640px) { .xaxis span.minor { display: none; } }
|
||||||
.chart.loading { opacity: .5; }
|
.chart.loading { opacity: .5; }
|
||||||
.chart .plot { position: absolute; left: 56px; right: 0; top: 0; bottom: 1px; }
|
.chart .plot { position: absolute; left: 56px; right: 0; top: 0; bottom: 1px; }
|
||||||
.chart .plot svg { width: 100%; height: 100%; display: block; overflow: visible; }
|
.chart .plot svg { width: 100%; height: 100%; display: block; overflow: visible; }
|
||||||
|
|||||||
@@ -384,8 +384,8 @@
|
|||||||
function pointLabel(t, range) {
|
function pointLabel(t, range) {
|
||||||
const d = new Date(t * 1000);
|
const d = new Date(t * 1000);
|
||||||
if (range === '24h') {
|
if (range === '24h') {
|
||||||
const hrs = Math.round((Date.now() - d.getTime()) / 3600000);
|
const hm = (x) => x.toLocaleTimeString(undefined, { hour: '2-digit', minute: '2-digit' });
|
||||||
return hrs <= 0 ? 'This hour' : hrs + ' h ago';
|
return hm(d) + '–' + hm(new Date(d.getTime() + 3600000));
|
||||||
}
|
}
|
||||||
return d.toLocaleDateString(undefined, { weekday: 'short', day: 'numeric', month: 'short' });
|
return d.toLocaleDateString(undefined, { weekday: 'short', day: 'numeric', month: 'short' });
|
||||||
}
|
}
|
||||||
@@ -430,9 +430,35 @@
|
|||||||
h('div', { class: 'gl top' }), h('div', { class: 'gl mid' }), h('div', { class: 'gl base' }),
|
h('div', { class: 'gl top' }), h('div', { class: 'gl mid' }), h('div', { class: 'gl base' }),
|
||||||
h('div', { class: 'yl top' }, fmtBytes(top)), h('div', { class: 'yl mid' }, fmtBytes(top / 2)),
|
h('div', { class: 'yl top' }, fmtBytes(top)), h('div', { class: 'yl mid' }, fmtBytes(top / 2)),
|
||||||
bars),
|
bars),
|
||||||
h('div', { class: 'xaxis' },
|
timeAxis(points.map((p) => p.t), range === '24h' ? 3600 : 86400));
|
||||||
h('span', null, pointLabel(points[0].t, range)),
|
}
|
||||||
h('span', null, range === '24h' ? 'now' : pointLabel(points[points.length - 1].t, range))));
|
|
||||||
|
// timeAxis labels the bottom of a chart with clock times or dates. Points
|
||||||
|
// are evenly spaced buckets of step seconds. Hourly buckets get the hour
|
||||||
|
// they start at, every 3 hours (6 on narrow screens), with the date at
|
||||||
|
// midnight; daily buckets get the date under the bar, every bar for a
|
||||||
|
// week and every 7th bar, counted back from today, for a month.
|
||||||
|
function timeAxis(ts, step) {
|
||||||
|
const n = ts.length;
|
||||||
|
const ticks = [];
|
||||||
|
if (step < 86400) {
|
||||||
|
for (let i = 0; i < n; i++) {
|
||||||
|
const d = new Date(ts[i] * 1000);
|
||||||
|
if (d.getMinutes() !== 0 || d.getHours() % 3 !== 0 || i === 0) continue;
|
||||||
|
const text = d.getHours() === 0
|
||||||
|
? d.toLocaleDateString(undefined, { weekday: 'short', day: 'numeric' })
|
||||||
|
: d.toLocaleTimeString(undefined, { hour: '2-digit', minute: '2-digit' });
|
||||||
|
ticks.push({ at: i / n, text, minor: d.getHours() % 6 !== 0, edge: true });
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
const every = n > 10 ? 7 : 1;
|
||||||
|
for (let i = n - 1; i >= 0; i -= every) {
|
||||||
|
const d = new Date(ts[i] * 1000);
|
||||||
|
ticks.push({ at: (i + 0.5) / n, text: d.toLocaleDateString(undefined, n > 10 ? { day: 'numeric', month: 'short' } : { weekday: 'short', day: 'numeric' }) });
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return h('div', { class: 'xaxis', 'aria-hidden': 'true' },
|
||||||
|
ticks.map((k) => h('span', { class: (k.minor ? 'minor' : '') + (k.edge ? ' edge' : ''), style: { left: (k.at * 100).toFixed(3) + '%' } }, k.text)));
|
||||||
}
|
}
|
||||||
|
|
||||||
// latencyChart draws the median as a line over a min–max band, one point
|
// latencyChart draws the median as a line over a min–max band, one point
|
||||||
@@ -489,7 +515,7 @@
|
|||||||
h('div', { class: 'gl top' }), h('div', { class: 'gl mid' }), h('div', { class: 'gl base' }),
|
h('div', { class: 'gl top' }), h('div', { class: 'gl mid' }), h('div', { class: 'gl base' }),
|
||||||
h('div', { class: 'yl top' }, fmtMs(top)), h('div', { class: 'yl mid' }, fmtMs(top / 2)),
|
h('div', { class: 'yl top' }, fmtMs(top)), h('div', { class: 'yl mid' }, fmtMs(top / 2)),
|
||||||
wrapEl),
|
wrapEl),
|
||||||
h('div', { class: 'xaxis' }, h('span', null, '24 h ago'), h('span', null, '12 h ago'), h('span', null, 'now')));
|
timeAxis(points.map((p) => p.t), 300));
|
||||||
}
|
}
|
||||||
|
|
||||||
// pairDialog creates an API token and shows it once, with the pairing QR
|
// pairDialog creates an API token and shows it once, with the pairing QR
|
||||||
|
|||||||
Reference in New Issue
Block a user