Add double-click to reset chart zoom on all charts

This commit is contained in:
Dione
2026-06-10 20:27:00 +00:00
parent e163f83963
commit fab41179d0

View File

@ -259,8 +259,8 @@ window.WalletManager = WalletManager;
<h1 class="text-3xl font-bold tracking-tight text-white"><span id="avg-sats-val">--</span> <span class="text-sm text-gray-500">sats</span></h1>
</div>
<div class="text-right">
<span class="text-lg font-bold" style="color: #FF7A00;">30D AVG</span>
<p class="text-xs text-gray-500 font-medium mt-0.5 uppercase tracking-tighter">Daily Acquisition</p>
<span class="text-lg font-bold" style="color: #FF7A00;">RUNNING AVG</span>
<p class="text-xs text-gray-500 font-medium mt-0.5 uppercase tracking-tighter">Sats per Day</p>
</div>
</div>
<div class="w-full h-52 mt-4" id="chart-container-sats"></div>
@ -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 '<div class="rounded-lg py-2 px-4 text-left"><div class="text-gray-400 text-[10px] font-semibold uppercase tracking-tighter mb-3">' + dateString + '</div><div class="text-orange-400 text-xs font-bold">₿ ' + btcVal + '/day</div><div class="text-white text-sm font-medium mt-1">' + dollarDay + '</div></div>'; } },
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());
});