From a91e5aceee7304906e20e427d8b0201776cf912b Mon Sep 17 00:00:00 2001 From: Davide Grilli Date: Tue, 28 Apr 2026 14:56:20 +0200 Subject: [PATCH] Auto-scale hashrate display to 1-999 range with correct unit Panel: MutationObserver intercepts the #hashrate DOM update and rescales the GH/s value from the API to the appropriate unit (MH/s, GH/s, TH/s, PH/s), also updating the unit label in the card header. Chart: nethashChart Y-axis tick callback detects the canvas ID and applies the same scaling logic per tick; a btcpHashrateUnit plugin updates the Y-axis title (e.g. "Hashrate (TH/s)") after each chart update to stay in sync with the data range --- public/js/custom.js | 134 +++++++++++++++++++++++++++++++++++++++----- 1 file changed, 119 insertions(+), 15 deletions(-) diff --git a/public/js/custom.js b/public/js/custom.js index fa9ab2f..2577407 100644 --- a/public/js/custom.js +++ b/public/js/custom.js @@ -1,23 +1,113 @@ $(document).ready(function() { - if (typeof Chart === 'undefined') return; - // ── Y-axis: abbreviate large numbers ───────────────────────── - // 1400000 → "1.4M", 45000 → "45K", 999 → "999" - function fmtAxis(value) { - var abs = Math.abs(value); - if (abs >= 1e9) return (value / 1e9).toFixed(1).replace(/\.0$/, '') + 'B'; - if (abs >= 1e6) return (value / 1e6).toFixed(1).replace(/\.0$/, '') + 'M'; - if (abs >= 1e3) return (value / 1e3).toFixed(1).replace(/\.0$/, '') + 'K'; - return value; + // ══════════════════════════════════════════════════════════════ + // HASHRATE AUTO-SCALING + // The server sends hashrate already divided by nethash_units ("G"), + // so values arrive in GH/s. We scale to keep the display 1–999. + // ══════════════════════════════════════════════════════════════ + + var HASH_UNITS = [ + { div: 1e6, label: 'PH/s' }, + { div: 1e3, label: 'TH/s' }, + { div: 1, label: 'GH/s' }, + { div: 1e-3, label: 'MH/s' }, + { div: 1e-6, label: 'KH/s' }, + { div: 1e-9, label: 'H/s' } + ]; + + // Returns {num, unit} where 1 ≤ |num| < 1000 (best fit) + function scaleFromGH(gh) { + var abs = Math.abs(gh); + for (var i = 0; i < HASH_UNITS.length; i++) { + if (abs >= HASH_UNITS[i].div) { + return { num: gh / HASH_UNITS[i].div, unit: HASH_UNITS[i].label }; + } + } + return { num: gh, unit: 'GH/s' }; } + // ── Panel: reformat #hashrate + update unit label ───────────── + function refreshHashratePanel() { + var $el = $('#hashrate'); + if (!$el.length) return; + + // Read raw text ("42,650.8351" or similar), strip formatting + var raw = $el.text().replace(/[,\s]/g, ''); + var ghVal = parseFloat(raw); + if (!isFinite(ghVal) || ghVal <= 0) return; + + var s = scaleFromGH(ghVal); + + // Format to max 4 significant digits, always 2 decimals + var decimals = (s.num >= 100) ? 1 : (s.num >= 10) ? 2 : 3; + var formatted = s.num.toFixed(decimals); + var parts = formatted.split('.'); + + // Temporarily disconnect observer to avoid loop + _hashrateObserver.disconnect(); + $el.html(parts[0] + '.' + parts[1] + ''); + _hashrateObserver.observe($el[0], { childList: true, subtree: true }); + + // Update the unit label "(GH/s)" → "(TH/s)" etc. + var $unitSpan = $el.closest('.card').find('.card-header span.small'); + if ($unitSpan.length) $unitSpan.text('(' + s.unit + ')'); + } + + var _hashrateObserver = new MutationObserver(function() { + refreshHashratePanel(); + }); + + // Start watching once #hashrate appears in the DOM + var _panelWatcher = new MutationObserver(function(mutations, obs) { + var el = document.getElementById('hashrate'); + if (el) { + obs.disconnect(); + _hashrateObserver.observe(el, { childList: true, subtree: true }); + } + }); + _panelWatcher.observe(document.body, { childList: true, subtree: true }); + + + // ══════════════════════════════════════════════════════════════ + // CHART.JS IMPROVEMENTS + // ══════════════════════════════════════════════════════════════ + + if (typeof Chart === 'undefined') return; + var font = { family: 'Inter, system-ui, sans-serif', size: 11 }; - // ── Linear scale (Y-axis on both charts) ───────────────────── + // ── Y-axis tick formatters ──────────────────────────────────── + + // Generic large-number formatter (difficulty, etc.) + function fmtAxis(value) { + var abs = Math.abs(value); + if (abs >= 1e9) return (value / 1e9).toFixed(1).replace(/\.0$/, '') + 'B'; + if (abs >= 1e6) return (value / 1e6).toFixed(1).replace(/\.0$/, '') + 'M'; + if (abs >= 1e3) return (value / 1e3).toFixed(1).replace(/\.0$/, '') + 'K'; + return value; + } + + // Hashrate formatter for nethashChart Y-axis ticks + // Scales GH/s values to the 1-999 range with the right unit + function fmtHashAxis(gh) { + if (gh === 0) return '0'; + var s = scaleFromGH(gh); + var decimals = (Math.abs(s.num) >= 100) ? 0 : (Math.abs(s.num) >= 10) ? 1 : 2; + return s.num.toFixed(decimals) + ' ' + s.unit; + } + + // ── Linear scale defaults ───────────────────────────────────── var lin = Chart.defaults.scales.linear; if (lin) { lin.ticks = Object.assign({}, lin.ticks, { - callback: fmtAxis, + // 'this' inside callback is the scale; this.chart.canvas.id identifies the chart + callback: function(value) { + if (this.chart && this.chart.canvas && + this.chart.canvas.id === 'nethashChart') { + return fmtHashAxis(value); + } + return fmtAxis(value); + }, font: font, color: 'rgba(200,160,255,0.85)', padding: 8, @@ -33,7 +123,6 @@ $(document).ready(function() { } // ── Time scale (X-axis) ─────────────────────────────────────── - // Reduce label density and avoid 90° rotation ['time', 'timeseries'].forEach(function(t) { var s = Chart.defaults.scales[t]; if (!s) return; @@ -45,9 +134,24 @@ $(document).ready(function() { minRotation: 0, padding: 6 }); - s.grid = Object.assign({}, s.grid, { - color: 'rgba(80,40,130,0.3)' - }); + s.grid = Object.assign({}, s.grid, { color: 'rgba(80,40,130,0.3)' }); + }); + + // ── Plugin: update nethashChart Y-axis title to match auto unit ── + Chart.register({ + id: 'btcpHashrateUnit', + afterUpdate: function(chart) { + if (!chart.canvas || chart.canvas.id !== 'nethashChart') return; + var yScale = chart.scales && chart.scales.y; + if (!yScale || !yScale.max) return; + + var s = scaleFromGH(yScale.max); + var newTitle = 'Hashrate (' + s.unit + ')'; + var opts = yScale.options; + if (opts && opts.title && opts.title.text !== newTitle) { + opts.title.text = newTitle; + } + } }); // ── Tooltip ───────────────────────────────────────────────────