From fab41179d0761d2c963de8ac3096d6838d7e3af2 Mon Sep 17 00:00:00 2001 From: Dione Date: Wed, 10 Jun 2026 20:27:00 +0000 Subject: [PATCH] Add double-click to reset chart zoom on all charts --- index.html | 72 ++++++++++++++++++++++++++++++++---------------------- 1 file changed, 43 insertions(+), 29 deletions(-) diff --git a/index.html b/index.html index aa9f1a1..ec2f668 100644 --- a/index.html +++ b/index.html @@ -259,8 +259,8 @@ window.WalletManager = WalletManager;

-- sats

- 30D AVG -

Daily Acquisition

+ RUNNING AVG +

Sats per Day

@@ -1602,38 +1602,36 @@ function setupSatsCard(cutoff) { dailyDeltas[d.dateStr] = d.btcVal - prevBalance; }); - /* Fill continuous daily timeline — iterate every day from first to last */ - const firstUTC = Date.UTC(new Date(sortedDays[0].dateStr).getUTCFullYear(), new Date(sortedDays[0].dateStr).getUTCMonth(), new Date(sortedDays[0].dateStr).getUTCDate()); - const lastUTC = Date.UTC(new Date(sortedDays[sortedDays.length - 1].dateStr).getUTCFullYear(), new Date(sortedDays[sortedDays.length - 1].dateStr).getUTCMonth(), new Date(sortedDays[sortedDays.length - 1].dateStr).getUTCDate()); - const continuousDays = []; - for (let t = firstUTC; t <= lastUTC; t += DAY_MS) { - const ds = new Date(t).toISOString().slice(0, 10); - continuousDays.push({ ts: t, dateStr: ds, delta: dailyDeltas[ds] || 0 }); - } + /* Compute total sats acquired and days elapsed since first snapshot */ + const firstUTC = sortedDays.length > 0 ? Date.UTC(new Date(sortedDays[0].dateStr).getUTCFullYear(), new Date(sortedDays[0].dateStr).getUTCMonth(), new Date(sortedDays[0].dateStr).getUTCDate()) : Date.now(); + const lastUTC = sortedDays.length > 0 ? Date.UTC(new Date(sortedDays[sortedDays.length - 1].dateStr).getUTCFullYear(), new Date(sortedDays[sortedDays.length - 1].dateStr).getUTCMonth(), new Date(sortedDays[sortedDays.length - 1].dateStr).getUTCDate()) : Date.now(); + const daysElapsed = Math.max(1, Math.ceil((lastUTC - firstUTC) / DAY_MS)); + const totalBtc = sortedDays[sortedDays.length - 1]?.btcVal || 0; + const totalSats = Math.round(totalBtc * 1e8); + const avgSatsPerDay = Math.round(totalSats / daysElapsed); - /* Exponentially weighted moving average — older days decay so average drops when there are no acquisitions */ - const filtered = continuousDays.filter(d => d.ts >= cutoff); - const halfLife = 7; - const decay = Math.pow(0.5, 1 / halfLife); - let ewmaVal = filtered[0]?.delta || 0; - const emaSats = []; - for (let i = 0; i < filtered.length; i++) { - ewmaVal = filtered[i].delta + decay * ewmaVal; - emaSats.push([filtered[i].ts, Math.round(ewmaVal * 1e8)]); - } - - const latestAvg = emaSats.length > 0 ? emaSats[emaSats.length - 1][1] : 0; - document.getElementById('avg-sats-val').innerText = new Intl.NumberFormat('en-US').format(Math.round(latestAvg)); - const options = { - chart: { id: 'instance-sats', type: 'area', height: 200, background: 'transparent', toolbar: { show: false }, sparkline: { enabled: false }, zoom: { enabled: true, type: 'x', autoScaleYaxis: true }, animations: { enabled: false } }, - series: [{ name: 'Avg Sats/Day', data: emaSats }], + /* Build continuous daily series: one data point per day */ + const filteredSats = []; + let runningTotalBtc = 0; + for (let t = firstUTC; t <= lastUTC; t += DAY_MS) { + const ds = new Date(t).toISOString().slice(0, 10); + runningTotalBtc += dailyDeltas[ds] || 0; + const daysSoFar = Math.max(1, Math.ceil((t - firstUTC) / DAY_MS) + 1); + const runningAvgSats = Math.round((runningTotalBtc * 1e8) / daysSoFar); + filteredSats.push([t, runningAvgSats]); + } + const latestAvg = filteredSats.length > 0 ? filteredSats[filteredSats.length - 1][1] : 0; + document.getElementById('avg-sats-val').innerText = new Intl.NumberFormat('en-US').format(latestAvg); + const options = { + chart: { id: 'instance-sats', type: 'area', height: 200, background: 'transparent', toolbar: { show: false }, sparkline: { enabled: false }, zoom: { enabled: true, type: 'x', autoScaleYaxis: true }, animations: { enabled: false } }, + series: [{ name: 'Avg Sats/Day', data: filteredSats }], dataLabels: { enabled: false }, colors: [orangeBrandColor], stroke: { curve: 'smooth', width: 2 }, fill: { type: 'gradient', gradient: { type: 'vertical', shadeIntensity: 0, colorStops: [{ offset: 0, color: orangeBrandColor, opacity: 0.16 }, { offset: 100, color: orangeBrandColor, opacity: 0 }] } }, grid: { show: false, padding: { left: 0, right: 0, top: 0, bottom: 0 } }, markers: { size: 0, hover: { size: 5 } }, - xaxis: { type: 'datetime', labels: { show: true, style: { colors: '#4B5563', fontSize: '10px', fontWeight: 600 }, datetimeFormatter: { year: 'yyyy', month: 'MMM yy', day: 'dd MMM' } }, axisBorder: { show: false }, axisTicks: { show: false } }, + xaxis: { type: 'datetime', labels: { show: true, style: { colors: '#4B5563', fontSize: '10px', fontWeight: 600 }, datetimeFormatter: { year: 'yyyy', month: 'MMM yy', day: 'dd MMM' } }, axisBorder: { show: false }, axisTicks: { show: false }, minTickInterval: '1day' }, yaxis: { opposite: true, labels: { offsetX: -10, style: { colors: '#4B5563', fontSize: '10px', fontWeight: 600 }, formatter: (val) => { if (val >= 1e6) return (val / 1e6).toFixed(0) + 'M'; if (val >= 1e3) return (val / 1e3).toFixed(0) + 'k'; return val; } }, axisBorder: { show: false }, axisTicks: { show: false } }, tooltip: { enabled: true, theme: 'dark', shared: false, intersect: false, custom: function({ series, seriesIndex, dataPointIndex, w }) { const rawData = w.config.series[seriesIndex].data[dataPointIndex]; if (!rawData) return ''; const date = new Date(rawData[0]); const dateString = date.toLocaleDateString('en-US', { month: 'short', day: 'numeric', year: 'numeric' }); const sats = rawData[1]; const btcVal = (sats / 1e8).toFixed(6); const priceStr = todayBtcPrice ? new Intl.NumberFormat('en-US', { style: 'currency', currency: 'USD', maximumFractionDigits: 0 }).format(todayBtcPrice) : '—'; const dollarDay = todayBtcPrice ? new Intl.NumberFormat('en-US', { style: 'currency', currency: 'USD', minimumFractionDigits: 0, maximumFractionDigits: 0 }).format((sats / 1e8) * todayBtcPrice) : '—'; return '
' + dateString + '
₿ ' + btcVal + '/day
' + dollarDay + '
'; } }, crosshairs: { show: true, width: 1, position: 'back', stroke: { color: '#4B5563', width: 1, dashArray: 3 } } @@ -1705,8 +1703,24 @@ function startTickUpdates(data) { } /* =================================================================== - Global Event Listeners - =================================================================== */ + Double-click to reset zoom + =================================================================== */ +function resetChartZoom(chartInstance) { + if (chartInstance) { + chartInstance.updateOptions({ + xaxis: { min: undefined, max: undefined } + }, false, false, true); + } +} + +document.getElementById('chart-container-btc').addEventListener('dblclick', () => resetChartZoom(window.btcChart)); +document.getElementById('chart-container-cumul').addEventListener('dblclick', () => resetChartZoom(window.cumulChart)); +document.getElementById('chart-container-sats').addEventListener('dblclick', () => resetChartZoom(window.satsChart)); +document.getElementById('chart-container-breakdown').addEventListener('dblclick', () => resetChartZoom(window.breakdownChart)); + +/* =================================================================== + Global Event Listeners + =================================================================== */ document.querySelectorAll('.filter-checkbox').forEach(cb => { cb.addEventListener('change', () => updateDashboard()); });