Skip to content

Commit

Permalink
improve percentage chart
Browse files Browse the repository at this point in the history
  • Loading branch information
domechn committed Nov 19, 2023
1 parent e10316a commit 0d89c2b
Show file tree
Hide file tree
Showing 6 changed files with 154 additions and 51 deletions.
19 changes: 6 additions & 13 deletions src/components/historical-data/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down Expand Up @@ -92,7 +92,7 @@ const App = ({
.catch((e) =>
toast({
description: e.message,
variant: "destructive"
variant: "destructive",
})
)
.finally(() => {
Expand Down Expand Up @@ -120,7 +120,7 @@ const App = ({
.catch((e) =>
toast({
description: e.message,
variant: "destructive"
variant: "destructive",
})
);
}
Expand Down Expand Up @@ -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}
Expand Down Expand Up @@ -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 (
<tr key={d.id}>
<td>
Expand Down Expand Up @@ -403,9 +398,7 @@ const App = ({
</table>
</div>
</Modal>
<div
className="flex justify-center items-center mb-5 text-gray-500 cursor-pointer"
>
<div className="flex justify-center items-center mb-5 text-gray-500 cursor-pointer">
<a
onClick={() => (pageNum > 1 ? setPageNum(pageNum - 1) : null)}
style={{
Expand Down
136 changes: 123 additions & 13 deletions src/components/latest-assets-percentage.tsx
Original file line number Diff line number Diff line change
@@ -1,18 +1,68 @@
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";

const App = ({ data }: { data: LatestAssetsPercentageData }) => {
const App = ({
currency,
data,
}: {
currency: CurrencyRateDetail;
data: LatestAssetsPercentageData;
}) => {
const size = useWindowSize();

const [appCacheDir, setAppCacheDir] = useState<string>("");

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());
}, [data]);

const options = {
maintainAspectRatio: false,
responsive: false,
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: {
Expand All @@ -33,14 +83,38 @@ 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,
},
],
Expand All @@ -52,16 +126,52 @@ const App = ({ data }: { data: LatestAssetsPercentageData }) => {
<Card>
<CardHeader className="flex flex-row items-center justify-between space-y-0 pb-2">
<CardTitle className="text-sm font-medium font-bold">
{size.width}
Percentage of Assets
</CardTitle>
</CardHeader>
<CardContent>
<div
style={{
height: Math.max((size.height || 100) / 2, 400),
}}
>
<Doughnut options={options as any} data={lineData()} />
<div className="grid gap-4 sm:grid-cols-2 md:grid-cols-5">
<div
className="sm:col-span-2 md:col-span-3"
style={{
height: Math.max((size.height || 100) / 2, 400),
}}
>
<Doughnut options={options as any} data={lineData()} />
</div>
<div className="sm:col-span-2 md:col-span-2">
<Table>
<TableBody>
{/* todo: paginate */}
{data.map((d) => (
<TableRow key={d.coin}>
<TableCell>
<div className="flex flex-row items-center">
<img
className="inline-block w-[20px] h-[20px] mr-2 rounded-full"
src={getImageApiPath(appCacheDir, d.coin)}
alt={d.coin}
/>
<div className="mr-1 font-bold">
{prettyPriceNumberToLocaleString(d.amount)}
</div>
<div className="text-gray-600">{d.coin}</div>
</div>
</TableCell>
<TableCell className="text-right">
<div className="text-gray-400">
{currency.symbol +
prettyNumberToLocaleString(
currencyWrapper(currency)(d.value)
)}
</div>
</TableCell>
</TableRow>
))}
</TableBody>
</Table>
</div>
</div>
</CardContent>
</Card>
Expand Down
9 changes: 3 additions & 6 deletions src/components/overview.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -27,7 +27,7 @@ const App = ({
topCoinsPercentageChangeData,
}: {
currency: CurrencyRateDetail;
pnlData: PNLData,
pnlData: PNLData;
totalValueData: TotalValueData;
latestAssetsPercentageData: LatestAssetsPercentageData;
assetChangeData: AssetChangeData;
Expand All @@ -43,12 +43,9 @@ const App = ({
assetChangeData={assetChangeData}
totalValueData={totalValueData}
></TotalValue>
<PNL
currency={currency}
pnlData={pnlData}
></PNL>
<PNL currency={currency} pnlData={pnlData}></PNL>
</div>
<LatestAssetsPercentage data={latestAssetsPercentageData} />
<LatestAssetsPercentage currency={currency} data={latestAssetsPercentageData} />
<CoinsAmountAndValueChange
currency={currency}
data={coinsAmountAndValueChangeData}
Expand Down
30 changes: 12 additions & 18 deletions src/middlelayers/charts.ts
Original file line number Diff line number Diff line change
Expand Up @@ -306,35 +306,29 @@ export async function queryAssetChange(size = 10): Promise<AssetChangeData> {

export async function queryLatestAssetsPercentage(): Promise<LatestAssetsPercentageData> {
const size = 1
const backgroundColors = generateRandomColors(11) // top 10 and others

const assets = groupAssetModelsListBySymbol(await queryAssets(size) || [])
if (assets.length === 0) {
return []
}

const latest = assets[0]
const backgroundColors = generateRandomColors(_(latest).size())

const total = _(latest).sumBy("value") + 10 ** -21 // avoid total is 0
const sortedLatest = _(latest).sortBy('value').reverse().value()
const top10 = _(sortedLatest).take(10).value()
const others = _(sortedLatest).drop(10).value()

const res: { coin: string, percentage: number }[] = []

_(top10).forEach(t => {
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,
Expand Down
2 changes: 2 additions & 0 deletions src/middlelayers/types.d.ts
Original file line number Diff line number Diff line change
Expand Up @@ -99,6 +99,8 @@ export type AssetChangeData = {

export type LatestAssetsPercentageData = {
coin: string
amount: number
value: number
percentage: number
chartColor: string
}[]
Expand Down
9 changes: 8 additions & 1 deletion src/utils/app.ts
Original file line number Diff line number Diff line change
@@ -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()
Expand All @@ -21,4 +23,9 @@ export async function trackEventWithClientID(event: string, props?: { [k: string
} catch (e) {
console.error("track event failed", e)
}
}
}

export function getImageApiPath(cacheDir: string, symbol: string) {
const filePath = `${cacheDir}assets/coins/${symbol.toLowerCase()}.png`
return convertFileSrc(filePath)
}

0 comments on commit 0d89c2b

Please sign in to comment.