add portfolio allocation donut chart on dashboard
This commit is contained in:
@@ -15,6 +15,9 @@
|
|||||||
import WalletIcon from '@lucide/svelte/icons/wallet'
|
import WalletIcon from '@lucide/svelte/icons/wallet'
|
||||||
import Loader from '$lib/components/loader.svelte'
|
import Loader from '$lib/components/loader.svelte'
|
||||||
import { frontendLog, frontendLogError } from '$lib/electrobun.js'
|
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)
|
let feError = $state<string | null>(null)
|
||||||
|
|
||||||
@@ -43,6 +46,33 @@
|
|||||||
|
|
||||||
const portfolioUsd = $derived(xrpTotalUsd + xmrTotalUsd)
|
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 hasXrp = $derived(xrpWallet.wallets.length > 0)
|
||||||
const hasXmr = $derived(moneroWallet.wallets.length > 0)
|
const hasXmr = $derived(moneroWallet.wallets.length > 0)
|
||||||
const noWallets = $derived(!hasXrp && !hasXmr)
|
const noWallets = $derived(!hasXrp && !hasXmr)
|
||||||
@@ -69,6 +99,8 @@
|
|||||||
maximumFractionDigits: dp,
|
maximumFractionDigits: dp,
|
||||||
})
|
})
|
||||||
|
|
||||||
|
const fmtPct = (v: number) => `${Math.round(v * 100)}%`
|
||||||
|
|
||||||
onMount(() => {
|
onMount(() => {
|
||||||
frontendLog('dashboard mounted')
|
frontendLog('dashboard mounted')
|
||||||
})
|
})
|
||||||
@@ -154,6 +186,43 @@
|
|||||||
{/if}
|
{/if}
|
||||||
</div>
|
</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">
|
<div class="grid grid-cols-1 gap-4 sm:grid-cols-2">
|
||||||
<button
|
<button
|
||||||
onclick={() => navigate('/xrp')}
|
onclick={() => navigate('/xrp')}
|
||||||
|
|||||||
Reference in New Issue
Block a user