From 80a7449688d0c0e95b1161701d88922f318e4273 Mon Sep 17 00:00:00 2001 From: Dione Date: Thu, 11 Jun 2026 08:43:25 +0000 Subject: [PATCH] Convert BTC Treasury Growth chart to stacked multi-wallet area chart --- index.html | 89 +++++++++++++++++++++++++++++++++++++++++++++++------- 1 file changed, 78 insertions(+), 11 deletions(-) diff --git a/index.html b/index.html index 696b660..a9069d7 100644 --- a/index.html +++ b/index.html @@ -413,6 +413,7 @@ const wm = new WalletManager(); wm.loadWallets(); let currentAggregatedSeries = []; +let currentWalletSeries = []; let currentNetHeld = 0; let currentBuyCost = 0; let currentBuyAmount = 0; @@ -1241,9 +1242,7 @@ function getOldestTransactionDate() { } /* Build per-day cumulative BTC series from addressSnapshots. - Forward-fills each wallet's balance for gap days and generates - a data point for every calendar day (like the sats chart) so the - y-axis uses daily steps. */ + Returns both aggregated total and per-wallet series. */ function buildCumulativeSeries(addresses) { const allDates = {}; addresses.forEach(addr => { @@ -1253,7 +1252,7 @@ function buildCumulativeSeries(addresses) { }); }); const snapshotDates = Object.keys(allDates).sort(); - if (snapshotDates.length === 0) return []; + if (snapshotDates.length === 0) return { total: [], perWallet: [] }; /* Build per-wallet forward-filled balance for every calendar day */ const firstUTC = Date.UTC(new Date(snapshotDates[0]).getUTCFullYear(), new Date(snapshotDates[0]).getUTCMonth(), new Date(snapshotDates[0]).getUTCDate()); @@ -1271,7 +1270,10 @@ function buildCumulativeSeries(addresses) { }); /* Walk every calendar day, forward-fill each wallet, aggregate */ - const result = []; + const totalResult = []; + const perWalletResult = {}; + addresses.forEach(addr => { perWalletResult[addr] = []; }); + for (let t = firstUTC; t <= lastUTC; t += DAY_MS) { const ds = new Date(t).toISOString().slice(0, 10); let dayTotal = 0; @@ -1283,11 +1285,12 @@ function buildCumulativeSeries(addresses) { else break; } dayTotal += balance; + perWalletResult[addr].push([t, balance]); }); - result.push([t, dayTotal]); + totalResult.push([t, dayTotal]); } - return result; + return { total: totalResult, perWallet: perWalletResult }; } function calculateAggregatedSeries() { @@ -1297,10 +1300,13 @@ function calculateAggregatedSeries() { if (selectedAddr.length === 0) { currentAggregatedSeries = []; + currentWalletSeries = []; return; } - currentAggregatedSeries = buildCumulativeSeries(selectedAddr); + const result = buildCumulativeSeries(selectedAddr); + currentAggregatedSeries = result.total; + currentWalletSeries = result.perWallet; } function calculateCurrentHoldings() { @@ -1344,7 +1350,9 @@ function updateDashboard() { calculateCurrentHoldings(); if (window.cumulChart) { - window.cumulChart.updateSeries([{ data: currentAggregatedSeries }]); + window.cumulChart.destroy(); + window.cumulChart = null; + refreshAllCharts(currentCutoffMs); } document.getElementById('net-held-val').innerText = currentNetHeld.toFixed(6); @@ -1614,8 +1622,67 @@ function updateBtcUI(price, percent) { } function setupCumulCard(cutoff) { - const filteredData = currentAggregatedSeries.filter(d => d[0] >= cutoff); - const options = getBaseChartOptions('instance-cumul', filteredData, orangeBrandColor, false); + const selectedAddr = getSelectedAddresses(); + const wallets = wm.getWallets(); + + const series = selectedAddr.map(addr => { + const w = wallets.find(x => x.address === addr); + const nickname = w ? (w.nickname || addr.slice(0, 6)) : addr.slice(0, 6); + const ws = currentWalletSeries[addr] || []; + return { + name: nickname, + data: ws.filter(d => d[0] >= cutoff) + }; + }); + + const colors = selectedAddr.map(addr => getColorForWallet(addr)); + + const gradientStops = colors.map(c => [ + { offset: 0, color: c, opacity: 0.35 }, + { offset: 100, color: c, opacity: 0 } + ]); + + const filteredTotal = currentAggregatedSeries.filter(d => d[0] >= cutoff); + let yMin = 0, yMax = 0; + if (filteredTotal.length > 0) { + const vals = filteredTotal.map(d => d[1]); + yMin = Math.min(...vals) * 0.95; + yMax = Math.max(...vals) * 1.05; + } + + const options = { + chart: { id: 'instance-cumul', type: 'area', height: 200, background: 'transparent', toolbar: { show: false }, sparkline: { enabled: false }, zoom: { enabled: true, type: 'x', autoScaleYaxis: true }, animations: { enabled: false }, stacked: true }, + stackType: 'normal', + series: series, + dataLabels: { enabled: false }, + colors: colors, + stroke: { curve: 'smooth', width: 2 }, + fill: { type: 'gradient', gradient: { type: 'vertical', shadeIntensity: 0, colorStops: gradientStops } }, + 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 }, minTickInterval: 86400000 }, + yaxis: { opposite: true, min: yMin, max: yMax, labels: { offsetX: -10, style: { colors: '#4B5563', fontSize: '10px', fontWeight: 600 }, formatter: (val) => val.toFixed(4) }, axisBorder: { show: false }, axisTicks: { show: false } }, + tooltip: { enabled: true, theme: 'dark', shared: true, intersect: false, custom: function({ series, seriesIndex, dataPointIndex, w }) { + const rawData = w.config.series[0].data[dataPointIndex]; + if (!rawData) return ''; + const date = new Date(rawData[0]); + const dateString = date.toLocaleDateString('en-US', { month: 'short', day: 'numeric', year: 'numeric' }); + let html = '
' + dateString + '
'; + let total = 0; + w.config.series.forEach((s, i) => { + const pt = s.data[dataPointIndex]; + if (pt) { + total += pt[1]; + html += '
' + s.name + '
' + pt[1].toFixed(6) + '
'; + } + }); + html += '
Total' + total.toFixed(6) + ' BTC
'; + html += '
'; + return html; + } }, + legend: { show: false }, + crosshairs: { show: true, width: 1, position: 'back', stroke: { color: '#4B5563', width: 1, dashArray: 3 } } + }; const chart = new ApexCharts(document.querySelector("#chart-container-cumul"), options); chart.render(); window.cumulChart = chart;