add portfolio allocation donut chart on dashboard

This commit is contained in:
2026-08-04 22:19:13 +08:00
parent da0c56658b
commit 3913889a22
+69
View File
@@ -15,6 +15,9 @@
import WalletIcon from '@lucide/svelte/icons/wallet'
import Loader from '$lib/components/loader.svelte'
import { frontendLog, frontendLogError } from '$lib/electrobun.js'
import { arc } from 'd3-shape'
import { tweened } from 'svelte/motion'
import { cubicOut } from 'svelte/easing'
let feError = $state<string | null>(null)
@@ -43,6 +46,33 @@
const portfolioUsd = $derived(xrpTotalUsd + xmrTotalUsd)
const xrpShare = $derived(portfolioUsd > 0 ? xrpTotalUsd / portfolioUsd : 0)
const xmrShare = $derived(
portfolioUsd > 0 ? xmrTotalUsd / portfolioUsd : 0,
)
const tweenedUsd = tweened(0, { duration: 600, easing: cubicOut })
const tweenedXrpShare = tweened(0, { duration: 600, easing: cubicOut })
$effect(() => {
tweenedUsd.set(portfolioUsd)
tweenedXrpShare.set(xrpShare)
})
const donut = $derived.by(() => {
if (portfolioUsd <= 0) return { xrp: '', xmr: '' }
const a = arc<{ startAngle: number; endAngle: number }>()
.innerRadius(62)
.outerRadius(80)
.padAngle(0.02)
const start = -Math.PI / 2
const endXrp = start + $tweenedXrpShare * 2 * Math.PI
return {
xrp: a({ startAngle: start, endAngle: endXrp }) ?? '',
xmr: a({ startAngle: endXrp, endAngle: start + 2 * Math.PI }) ?? '',
}
})
const hasXrp = $derived(xrpWallet.wallets.length > 0)
const hasXmr = $derived(moneroWallet.wallets.length > 0)
const noWallets = $derived(!hasXrp && !hasXmr)
@@ -69,6 +99,8 @@
maximumFractionDigits: dp,
})
const fmtPct = (v: number) => `${Math.round(v * 100)}%`
onMount(() => {
frontendLog('dashboard mounted')
})
@@ -154,6 +186,43 @@
{/if}
</div>
<Card>
<CardHeader class="pb-2">
<CardTitle class="text-base">Portfolio Allocation</CardTitle>
<CardDescription class="text-xs">
Share of your balance by asset
</CardDescription>
</CardHeader>
<CardContent class="flex flex-col items-center gap-5 py-6">
<div class="relative">
<svg viewBox="-100 -100 200 200" class="size-48">
<path d={donut.xrp} fill="#0EA5E9" />
<path d={donut.xmr} fill="#F97316" />
</svg>
<div
class="absolute inset-0 flex flex-col items-center justify-center">
<p
class="text-lg font-semibold tabular-nums">$
{fmtUsd($tweenedUsd)}
</p>
<p class="text-xs text-muted-foreground">
Portfolio
</p>
</div>
</div>
<div class="flex gap-5 text-xs">
<span class="flex items-center gap-1.5">
<span class="size-2.5 rounded-full bg-sky-500"></span>
XRP · {fmtPct($tweenedXrpShare)}
</span>
<span class="flex items-center gap-1.5">
<span class="size-2.5 rounded-full bg-orange-500"></span>
XMR · {fmtPct(1 - $tweenedXrpShare)}
</span>
</div>
</CardContent>
</Card>
<div class="grid grid-cols-1 gap-4 sm:grid-cols-2">
<button
onclick={() => navigate('/xrp')}