Skip to content

Commit

Permalink
Merge pull request #332 from greymass/asynchronous-chart-loading
Browse files Browse the repository at this point in the history
feat: async chart loading
  • Loading branch information
aaroncox authored Jan 28, 2025
2 parents 4b9446c + aed7765 commit 84093df
Show file tree
Hide file tree
Showing 9 changed files with 266 additions and 337 deletions.
79 changes: 57 additions & 22 deletions src/lib/components/chart/chart-container.svelte
Original file line number Diff line number Diff line change
@@ -1,31 +1,61 @@
<script lang="ts">
import type { Snippet } from 'svelte';
import dayjs from 'dayjs';
import Card from '../layout/box/card.svelte';
import Select from '../select/select.svelte';
import type { ExtendedSelectOption } from '../select/types';
import type { HistoricalPrice } from '$lib/types';
import LineChart from './line-chart.svelte';
import * as m from '$lib/paraglide/messages';
interface Props {
pair: string;
currentPrice: string;
percentChange: string;
range: ExtendedSelectOption[];
selectedRange: ExtendedSelectOption;
children: Snippet;
startDate: string;
endDate?: string;
data: HistoricalPrice[];
type: 'line';
}
let {
pair,
currentPrice,
percentChange,
range,
selectedRange = $bindable(),
children,
startDate,
endDate = 'Today'
}: Props = $props();
let { pair, data, ...props }: Props = $props();
const range: ExtendedSelectOption[] = [
{ label: '1D', value: 1 },
{ label: '1W', value: 7 },
{ label: '1M', value: 30 }
// { label: '1Y', value: 365 } // We're currently only getting data for the last 30 days
];
let selectedRange: ExtendedSelectOption = $state(range[1]);
const MAX_NUM_POINTS = 100; // Maximum number of points to display on the chart
let dataRange = $derived.by(() => {
if (data.length === 0) return [];
const rangeEndDate = dayjs(data[0].date);
const rangeStartDate = rangeEndDate.subtract(Number(selectedRange.value), 'day');
const filteredData = data.filter(({ date }) => dayjs(date).isAfter(rangeStartDate));
// If we have more points than MAX_NUM_POINTS, sample them evenly
if (filteredData.length > MAX_NUM_POINTS) {
const result = [];
const step = filteredData.length / MAX_NUM_POINTS;
for (let i = 0; i < MAX_NUM_POINTS; i++) {
const index = Math.floor(i * step);
result.push(filteredData[index]);
}
return result;
}
return filteredData;
});
let startDate = $derived(dataRange[dataRange.length - 1].date.toLocaleDateString());
let currentPoint = $derived(dataRange[0]);
let currentPrice = $derived(String(currentPoint.value));
let percentChange = $derived.by(() => {
const current = Number(currentPoint.value.quantity);
const initial = Number(dataRange[dataRange.length - 1].value.quantity);
return (((current - initial) / current) * 100).toFixed(2) + '%';
});
</script>

<Card class="relative">
Expand All @@ -37,11 +67,16 @@
</div>
<Select id="range-select" options={range} bind:selected={selectedRange} />
</header>

<!-- w-99 is a hack to get responsive charts, w-full doesn't work -->
<div class="canvas-container relative h-auto w-[99%]">
{@render children()}
{#if props.type === 'line'}
<LineChart label={pair} data={dataRange} />
{/if}
</div>
<hr class="h-px border-0 bg-shark-200/50" />

<hr class="h-px border-0 bg-mineShaft-800" />

<div class="flex items-center justify-between font-medium">
<span class="text-muted">{startDate}</span>
<div class="flex gap-4">
Expand All @@ -50,6 +85,6 @@
<span class="text-[#00ED97]">{pair}</span>
</div>
</div>
<span class="text-muted">{endDate}</span>
<span class="text-muted">{m.common_today()}</span>
</div>
</Card>
162 changes: 35 additions & 127 deletions src/lib/components/chart/eospricehistory.svelte
Original file line number Diff line number Diff line change
@@ -1,134 +1,42 @@
<script lang="ts">
import { onMount } from 'svelte';
import dayjs from 'dayjs';
import { Chart } from 'chart.js';
import 'chart.js/auto';
import { getContext } from 'svelte';
import { Asset } from '@wharfkit/antelope';
import type { ExtendedSelectOption } from '../select/types';
import ChartContainer from './chart-container.svelte';
interface Props {
data: { date: Date; value: Asset }[];
}
let { data }: Props = $props();
let ctx: HTMLCanvasElement;
let chart: Chart<'line'>;
const range: ExtendedSelectOption[] = [
{ label: '1D', value: 1 },
{ label: '1W', value: 7 },
{ label: '1M', value: 30 }
// { label: '1Y', value: 365 } // We're currently only getting data for the last 30 days
];
const MAX_NUM_POINTS = 100; // Maximum number of points to display on the chart
let selectedRange: ExtendedSelectOption = $state(range[1]);
let dataRange = $derived.by(() => {
if (data.length === 0) return [];
const rangeEndDate = dayjs(data[0].date);
const rangeStartDate = rangeEndDate.subtract(Number(selectedRange.value), 'day');
const filteredData = data.filter(({ date }) => dayjs(date).isAfter(rangeStartDate));
// If we have more points than MAX_NUM_POINTS, sample them evenly
if (filteredData.length > MAX_NUM_POINTS) {
const result = [];
const step = filteredData.length / MAX_NUM_POINTS;
for (let i = 0; i < MAX_NUM_POINTS; i++) {
const index = Math.floor(i * step);
result.push(filteredData[index]);
import type { UnicoveContext } from '$lib/state/client.svelte';
import type { HistoricalPrice } from '$lib/types';
import Loading from './loading.svelte';
const { network } = getContext<UnicoveContext>('state');
type Price = { date: string; value: number };
type APIResponse = Price[] | { error: string };
const fetchTokenPrices = async () => {
try {
const response = await fetch(`/${network}/api/metrics/marketprice/token`);
const parsedTokenResponse: APIResponse = await response.json();
if (Array.isArray(parsedTokenResponse) && parsedTokenResponse.length) {
return parsedTokenResponse.map(
(price: Price) =>
({
date: new Date(price.date),
value: Asset.from(price.value / 10000, '4,USD')
}) as HistoricalPrice
);
} else if ('error' in parsedTokenResponse && parsedTokenResponse.error) {
throw new Error(String(parsedTokenResponse.error));
} else {
throw new Error('Error fetching RAM prices');
}
return result;
} catch (e) {
throw new Error(String(e));
}
return filteredData;
});
let currentPoint = $derived(dataRange[0]);
let currentPrice = $derived(String(currentPoint.value));
let percentChange = $derived.by(() => {
const current = Number(currentPoint.value.quantity);
const initial = Number(dataRange[dataRange.length - 1].value.quantity);
return (((current - initial) / current) * 100).toFixed(2) + '%';
});
const labels = $derived(dataRange.map(({ date }) => String(date.toLocaleDateString())));
const values = $derived(dataRange.map(({ value }) => Number(value.quantity)));
onMount(() => {
chart = new Chart(ctx, {
type: 'line',
data: {
labels: labels,
datasets: [
{
label: 'USD',
data: values,
fill: false,
borderColor: '#00ED97',
pointBorderWidth: 0
}
]
},
options: {
normalized: true,
responsive: true,
maintainAspectRatio: false,
interaction: {
mode: 'nearest',
intersect: false
},
scales: {
x: {
reverse: true,
border: {
display: false
},
grid: {
display: false
},
ticks: {
display: false
}
},
y: {
border: {
display: false
},
grid: {
display: false
}
}
},
plugins: {
legend: {
display: false
}
}
}
});
});
$effect(() => {
chart.data.labels = labels;
chart.data.datasets[0].data = values;
chart.update();
});
let startDate = $derived(dataRange[dataRange.length - 1].date.toLocaleDateString());
};
</script>

<ChartContainer
pair="EOS/USD"
{currentPrice}
{percentChange}
{startDate}
{range}
bind:selectedRange
>
<canvas bind:this={ctx}></canvas>
</ChartContainer>
{#await fetchTokenPrices()}
<Loading pair="EOS/USD" />
{:then eosPrices}
<ChartContainer pair="EOS/USD" data={eosPrices} type="line" />
{/await}
81 changes: 81 additions & 0 deletions src/lib/components/chart/line-chart.svelte
Original file line number Diff line number Diff line change
@@ -0,0 +1,81 @@
<script lang="ts">
import { onMount } from 'svelte';
import { Chart } from 'chart.js';
import 'chart.js/auto';
import type { HistoricalPrice } from '$lib/types';
let ctx: HTMLCanvasElement;
let chart: Chart<'line'>;
interface Props {
data: HistoricalPrice[];
label: string;
}
let { data, label }: Props = $props();
const labels = $derived(data.map(({ date }) => String(date.toLocaleDateString())));
const values = $derived(data.map(({ value }) => Number(value.quantity)));
onMount(() => {
chart = new Chart(ctx, {
type: 'line',
data: {
labels: labels,
datasets: [
{
label: label,
data: values,
fill: false,
borderColor: '#00ED97',
pointBorderWidth: 0
}
]
},
options: {
normalized: true,
responsive: true,
maintainAspectRatio: false,
interaction: {
mode: 'nearest',
intersect: false
},
scales: {
x: {
reverse: true,
border: {
display: false
},
grid: {
display: false
},
ticks: {
display: false
}
},
y: {
border: {
display: false
},
grid: {
display: false
}
}
},
plugins: {
legend: {
display: false
}
}
}
});
});
$effect(() => {
chart.data.labels = labels;
chart.data.datasets[0].data = values;
chart.update();
});
</script>

<canvas bind:this={ctx}></canvas>
Loading

0 comments on commit 84093df

Please sign in to comment.