From 373ad85a31bcd834ad31831916bfaa6105514417 Mon Sep 17 00:00:00 2001
From: domchan <31119455+domechn@users.noreply.github.com>
Date: Mon, 20 Nov 2023 14:46:27 -0600
Subject: [PATCH] improve percentage chart (#181)
* improve percentage chart
* tweak
* fix layout
* hide card title
* support page
* rm commentor
---
src/components/historical-data/index.tsx | 19 +-
src/components/latest-assets-percentage.tsx | 191 ++++++++++++++++++--
src/components/overview.tsx | 28 +--
src/middlelayers/charts.ts | 30 ++-
src/middlelayers/types.d.ts | 2 +
src/utils/app.ts | 9 +-
6 files changed, 219 insertions(+), 60 deletions(-)
diff --git a/src/components/historical-data/index.tsx b/src/components/historical-data/index.tsx
index 05406ea..51ac7a8 100644
--- a/src/components/historical-data/index.tsx
+++ b/src/components/historical-data/index.tsx
@@ -19,9 +19,9 @@ import {
import Modal from "../common/modal";
import { downloadCoinLogos } from "@/middlelayers/data";
import { appCacheDir as getAppCacheDir } from "@tauri-apps/api/path";
-import { convertFileSrc } from "@tauri-apps/api/tauri";
import { useWindowSize } from "@/utils/hook";
import ImageStack from "../common/image-stack";
+import { getImageApiPath } from "@/utils/app";
type RankData = {
id: number;
@@ -92,7 +92,7 @@ const App = ({
.catch((e) =>
toast({
description: e.message,
- variant: "destructive"
+ variant: "destructive",
})
)
.finally(() => {
@@ -120,7 +120,7 @@ const App = ({
.catch((e) =>
toast({
description: e.message,
- variant: "destructive"
+ variant: "destructive",
})
);
}
@@ -208,7 +208,7 @@ const App = ({
.sortBy("value")
.reverse()
.take(7)
- .map((a) => getImageApiPath(a.symbol))
+ .map((a) => getImageApiPath(appCacheDir, a.symbol))
.value()}
imageWidth={25}
imageHeight={25}
@@ -260,15 +260,10 @@ const App = ({
.value();
}
- function getImageApiPath(symbol: string) {
- const filePath = `${appCacheDir}assets/coins/${symbol.toLowerCase()}.png`;
- return convertFileSrc(filePath);
- }
-
function renderDetailPage(data: RankData[]) {
return _(data)
.map((d) => {
- const apiPath = getImageApiPath(d.symbol);
+ const apiPath = getImageApiPath(appCacheDir, d.symbol);
return (
@@ -403,9 +398,7 @@ const App = ({
-
+
(pageNum > 1 ? setPageNum(pageNum - 1) : null)}
style={{
diff --git a/src/components/latest-assets-percentage.tsx b/src/components/latest-assets-percentage.tsx
index 511feec..1d396fc 100644
--- a/src/components/latest-assets-percentage.tsx
+++ b/src/components/latest-assets-percentage.tsx
@@ -1,10 +1,63 @@
import { Doughnut } from "react-chartjs-2";
-import { useWindowSize } from "@/utils/hook";
-import { LatestAssetsPercentageData } from "@/middlelayers/types";
+import {
+ CurrencyRateDetail,
+ LatestAssetsPercentageData,
+} from "@/middlelayers/types";
import { Card, CardContent, CardHeader, CardTitle } from "./ui/card";
+import _ from "lodash";
+import { useEffect, useState } from "react";
+import { appCacheDir as getAppCacheDir } from "@tauri-apps/api/path";
+import { Table, TableBody, TableCell, TableRow } from "./ui/table";
+import { getImageApiPath } from "@/utils/app";
+import {
+ currencyWrapper,
+ prettyNumberToLocaleString,
+ prettyPriceNumberToLocaleString,
+} from "@/utils/currency";
+import { downloadCoinLogos } from "@/middlelayers/data";
+import { Button } from "./ui/button";
+import { Separator } from "./ui/separator";
+import {
+ ArrowLeftIcon,
+ ChevronLeftIcon,
+ ChevronRightIcon,
+} from "@radix-ui/react-icons";
-const App = ({ data }: { data: LatestAssetsPercentageData }) => {
- const size = useWindowSize();
+const App = ({
+ currency,
+ data,
+}: {
+ currency: CurrencyRateDetail;
+ data: LatestAssetsPercentageData;
+}) => {
+ const [appCacheDir, setAppCacheDir] = useState("");
+ const [dataPage, setDataPage] = useState(0);
+ const [maxDataPage, setMaxDataPage] = useState(0);
+ const pageSize = 5;
+
+ useEffect(() => {
+ getAppCacheDir().then((dir) => {
+ setAppCacheDir(dir);
+ });
+ }, []);
+
+ const [percentageData, setPercentageData] = useState<
+ {
+ coin: string;
+ percentage: number;
+ chartColor: string;
+ }[]
+ >(data);
+
+ useEffect(() => {
+ setPercentageData(splitTopAndOtherData(data));
+
+ // download coin logos
+ downloadCoinLogos(_(data).map("coin").value());
+
+ // set max data page
+ setMaxDataPage(Math.floor(data.length / pageSize));
+ }, [data]);
const options = {
maintainAspectRatio: false,
@@ -12,7 +65,14 @@ const App = ({ data }: { data: LatestAssetsPercentageData }) => {
plugins: {
// text is set for resizing
title: { display: false, text: "Percentage of Assets" },
- legend: { labels: { font: {} } },
+ legend: {
+ display: true,
+ position: "right",
+ font: {
+ size: 14,
+ },
+ labels: { font: {} },
+ },
datalabels: {
color: "white",
font: {
@@ -33,35 +93,134 @@ const App = ({ data }: { data: LatestAssetsPercentageData }) => {
},
};
+ function splitTopAndOtherData(d: LatestAssetsPercentageData) {
+ const count = 5;
+ if (d.length <= count) {
+ return d;
+ }
+ const top = _(d).sortBy("percentage").reverse().take(count).value();
+ console.log(top);
+ const other = _(d).sortBy("percentage").reverse().drop(count).value();
+
+ return _([
+ ...top,
+ other
+ ? {
+ coin: "Other",
+ percentage: _(other).map("percentage").sum(),
+ chartColor: other[0]?.chartColor ?? "#4B5563",
+ }
+ : null,
+ ])
+ .compact()
+ .value();
+ }
+
function lineData() {
+ const d = percentageData;
return {
- labels: data.map((coin) => coin.coin),
+ labels: d.map((coin) => coin.coin),
datasets: [
{
- data: data.map((coin) => coin.percentage),
- borderColor: data.map((coin) => coin.chartColor),
- backgroundColor: data.map((coin) => coin.chartColor),
+ data: d.map((coin) => coin.percentage),
+ borderColor: d.map((coin) => coin.chartColor),
+ backgroundColor: d.map((coin) => coin.chartColor),
borderWidth: 1,
},
],
};
}
+ function renderDoughnut() {
+ return ;
+ }
+
+ function renderTokenHoldingList() {
+ return (
+ <>
+
+
+ Token holding
+
+
+
+
+ {dataPage + 1} {"/"} {maxDataPage + 1}
+
+
+
+
+
+
+
+ {data
+ .slice(dataPage * pageSize, (dataPage + 1) * pageSize)
+ .map((d) => (
+
+
+
+
+
+ {prettyPriceNumberToLocaleString(d.amount)}
+
+ {d.coin}
+
+
+
+
+ {currency.symbol +
+ prettyNumberToLocaleString(
+ currencyWrapper(currency)(d.value)
+ )}
+
+
+
+ ))}
+
+
+ >
+ );
+ }
+
return (
- Percentage of Assets
+ {/* Percentage of Assets */}
-
-
+
+
+ {renderDoughnut()}
+
+
+ {renderTokenHoldingList()}
+
diff --git a/src/components/overview.tsx b/src/components/overview.tsx
index 6d4483b..b922eee 100644
--- a/src/components/overview.tsx
+++ b/src/components/overview.tsx
@@ -27,7 +27,7 @@ const App = ({
topCoinsPercentageChangeData,
}: {
currency: CurrencyRateDetail;
- pnlData: PNLData,
+ pnlData: PNLData;
totalValueData: TotalValueData;
latestAssetsPercentageData: LatestAssetsPercentageData;
assetChangeData: AssetChangeData;
@@ -37,18 +37,22 @@ const App = ({
}) => {
return (
-
-
-
+
-
+
{
export async function queryLatestAssetsPercentage(): Promise {
const size = 1
- const backgroundColors = generateRandomColors(11) // top 10 and others
const assets = groupAssetModelsListBySymbol(await queryAssets(size) || [])
if (assets.length === 0) {
@@ -314,27 +313,22 @@ export async function queryLatestAssetsPercentage(): Promise {
- res.push({
- coin: t.symbol,
- percentage: t.value / total * 100,
- })
- })
+ const res: {
+ coin: string,
+ percentage: number,
+ amount: number,
+ value: number,
+ }[] = _(latest).map(t => ({
+ coin: t.symbol,
+ amount: t.amount,
+ value: t.value,
+ percentage: t.value / total * 100,
- if (others.length > 0) {
- res.push({
- coin: 'Others',
- percentage: _(others).sumBy('value') / total * 100,
- })
- }
+ })).value()
return _(res).sortBy('percentage').reverse().map((v, idx) => ({
...v,
diff --git a/src/middlelayers/types.d.ts b/src/middlelayers/types.d.ts
index 4602da1..40bed98 100644
--- a/src/middlelayers/types.d.ts
+++ b/src/middlelayers/types.d.ts
@@ -99,6 +99,8 @@ export type AssetChangeData = {
export type LatestAssetsPercentageData = {
coin: string
+ amount: number
+ value: number
percentage: number
chartColor: string
}[]
diff --git a/src/utils/app.ts b/src/utils/app.ts
index fcab9f8..976a05e 100644
--- a/src/utils/app.ts
+++ b/src/utils/app.ts
@@ -1,6 +1,8 @@
import * as api from '@tauri-apps/api'
import { getClientIDConfiguration } from '../middlelayers/configuration'
import { trackEvent } from '@aptabase/tauri'
+import { appCacheDir } from "@tauri-apps/api/path"
+import { convertFileSrc } from "@tauri-apps/api/tauri"
export async function getVersion() {
return api.app.getVersion()
@@ -21,4 +23,9 @@ export async function trackEventWithClientID(event: string, props?: { [k: string
} catch (e) {
console.error("track event failed", e)
}
-}
\ No newline at end of file
+}
+
+export function getImageApiPath(cacheDir: string, symbol: string) {
+ const filePath = `${cacheDir}assets/coins/${symbol.toLowerCase()}.png`
+ return convertFileSrc(filePath)
+}
|