mirror of
https://github.com/mempool/mempool.git
synced 2025-01-19 05:34:03 +01:00
Merge pull request #5153 from mempool/natsoni/address-history-chart-usd
Add USD to address balance history chart
This commit is contained in:
commit
8b0015b3ff
@ -2,7 +2,7 @@
|
||||
|
||||
<div [class.full-container]="!widget">
|
||||
<ng-container *ngIf="!error">
|
||||
<div [class]="!widget ? 'chart' : 'chart-widget'" *browserOnly [style]="{ height: widget ? ((height + 20) + 'px') : null}" echarts [initOpts]="chartInitOptions" [options]="chartOptions"
|
||||
<div [class]="!widget ? 'chart' : 'chart-widget'" *browserOnly [style]="{ height: widget ? ((height + 20) + 'px') : null, paddingBottom: !widget && !allowZoom ? '10px' : null}" echarts [initOpts]="chartInitOptions" [options]="chartOptions"
|
||||
(chartInit)="onChartInit($event)">
|
||||
</div>
|
||||
<div class="text-center loadingGraphs" *ngIf="isLoading">
|
||||
|
@ -46,7 +46,6 @@
|
||||
display: flex;
|
||||
flex: 1;
|
||||
width: 100%;
|
||||
padding-bottom: 10px;
|
||||
padding-right: 10px;
|
||||
}
|
||||
.chart-widget {
|
||||
|
@ -1,13 +1,16 @@
|
||||
import { ChangeDetectionStrategy, ChangeDetectorRef, Component, Inject, Input, LOCALE_ID, OnChanges, OnDestroy, SimpleChanges } from '@angular/core';
|
||||
import { ChangeDetectionStrategy, ChangeDetectorRef, Component, Inject, Input, LOCALE_ID, NgZone, OnChanges, OnDestroy, SimpleChanges } from '@angular/core';
|
||||
import { echarts, EChartsOption } from '../../graphs/echarts';
|
||||
import { BehaviorSubject, Observable, Subscription, combineLatest, of } from 'rxjs';
|
||||
import { catchError } from 'rxjs/operators';
|
||||
import { catchError, map, switchMap, tap } from 'rxjs/operators';
|
||||
import { AddressTxSummary, ChainStats } from '../../interfaces/electrs.interface';
|
||||
import { ElectrsApiService } from '../../services/electrs-api.service';
|
||||
import { AmountShortenerPipe } from '../../shared/pipes/amount-shortener.pipe';
|
||||
import { Router } from '@angular/router';
|
||||
import { RelativeUrlPipe } from '../../shared/pipes/relative-url/relative-url.pipe';
|
||||
import { StateService } from '../../services/state.service';
|
||||
import { PriceService } from '../../services/price.service';
|
||||
import { FiatCurrencyPipe } from '../../shared/pipes/fiat-currency.pipe';
|
||||
import { FiatShortenerPipe } from '../../shared/pipes/fiat-shortener.pipe';
|
||||
|
||||
const periodSeconds = {
|
||||
'1d': (60 * 60 * 24),
|
||||
@ -44,7 +47,13 @@ export class AddressGraphComponent implements OnChanges, OnDestroy {
|
||||
@Input() widget: boolean = false;
|
||||
|
||||
data: any[] = [];
|
||||
fiatData: any[] = [];
|
||||
hoverData: any[] = [];
|
||||
conversions: any;
|
||||
allowZoom: boolean = false;
|
||||
initialRight = this.right;
|
||||
initialLeft = this.left;
|
||||
selected = { [$localize`:@@7e69426bd97a606d8ae6026762858e6e7c86a1fd:Balance`]: true, 'Fiat': false };
|
||||
|
||||
subscription: Subscription;
|
||||
redraw$: BehaviorSubject<boolean> = new BehaviorSubject(false);
|
||||
@ -66,6 +75,10 @@ export class AddressGraphComponent implements OnChanges, OnDestroy {
|
||||
private amountShortenerPipe: AmountShortenerPipe,
|
||||
private cd: ChangeDetectorRef,
|
||||
private relativeUrlPipe: RelativeUrlPipe,
|
||||
private priceService: PriceService,
|
||||
private fiatCurrencyPipe: FiatCurrencyPipe,
|
||||
private fiatShortenerPipe: FiatShortenerPipe,
|
||||
private zone: NgZone,
|
||||
) {}
|
||||
|
||||
ngOnChanges(changes: SimpleChanges): void {
|
||||
@ -86,10 +99,39 @@ export class AddressGraphComponent implements OnChanges, OnDestroy {
|
||||
this.error = `Failed to fetch address balance history: ${e?.status || ''} ${e?.statusText || 'unknown error'}`;
|
||||
return of(null);
|
||||
}),
|
||||
))
|
||||
]).subscribe(([redraw, addressSummary]) => {
|
||||
)),
|
||||
this.stateService.conversions$
|
||||
]).pipe(
|
||||
switchMap(([redraw, addressSummary, conversions]) => {
|
||||
this.conversions = conversions;
|
||||
if (addressSummary) {
|
||||
let extendedSummary = this.extendSummary(addressSummary);
|
||||
return this.priceService.getPriceByBulk$(extendedSummary.map(d => d.time), 'USD').pipe(
|
||||
tap((prices) => {
|
||||
if (prices.length !== extendedSummary.length) {
|
||||
extendedSummary = extendedSummary.map(item => ({ ...item, price: 0 }));
|
||||
} else {
|
||||
extendedSummary = extendedSummary.map((item, index) => {
|
||||
let price = 0;
|
||||
if (prices[index].price) {
|
||||
price = prices[index].price['USD'];
|
||||
} else if (this.conversions && this.conversions['USD']) {
|
||||
price = this.conversions['USD'];
|
||||
}
|
||||
return { ...item, price: price }
|
||||
});
|
||||
}
|
||||
}),
|
||||
map(() => [redraw, extendedSummary, conversions])
|
||||
)
|
||||
} else {
|
||||
return of([redraw, addressSummary, conversions]);
|
||||
}
|
||||
})
|
||||
).subscribe(([redraw, addressSummary, conversions]) => {
|
||||
if (addressSummary) {
|
||||
this.error = null;
|
||||
this.allowZoom = addressSummary.length > 100 && !this.widget;
|
||||
this.prepareChartOptions(addressSummary);
|
||||
}
|
||||
this.isLoading = false;
|
||||
@ -101,25 +143,37 @@ export class AddressGraphComponent implements OnChanges, OnDestroy {
|
||||
}
|
||||
}
|
||||
|
||||
prepareChartOptions(summary): void {
|
||||
prepareChartOptions(summary: AddressTxSummary[]) {
|
||||
if (!summary || !this.stats) {
|
||||
return;
|
||||
}
|
||||
|
||||
let total = (this.stats.funded_txo_sum - this.stats.spent_txo_sum);
|
||||
this.data = summary.map(d => {
|
||||
const balance = total;
|
||||
total -= d.value;
|
||||
return [d.time * 1000, balance, d];
|
||||
const processData = summary.map(d => {
|
||||
const balance = total;
|
||||
const fiatBalance = total * d.price / 100_000_000;
|
||||
total -= d.value;
|
||||
return {
|
||||
time: d.time * 1000,
|
||||
balance,
|
||||
fiatBalance,
|
||||
d
|
||||
};
|
||||
}).reverse();
|
||||
|
||||
this.data = processData.filter(({ d }) => d.txid !== undefined).map(({ time, balance, d }) => [time, balance, d]);
|
||||
this.fiatData = processData.map(({ time, fiatBalance, balance, d }) => [time, fiatBalance, d, balance]);
|
||||
|
||||
const now = Date.now();
|
||||
if (this.period !== 'all') {
|
||||
const now = Date.now();
|
||||
const start = now - (periodSeconds[this.period] * 1000);
|
||||
this.data = this.data.filter(d => d[0] >= start);
|
||||
this.data.push(
|
||||
{value: [now, this.stats.funded_txo_sum - this.stats.spent_txo_sum], symbol: 'none', tooltip: { show: false }}
|
||||
);
|
||||
const startFiat = this.data[0]?.[0] ?? start; // Make sure USD data starts at the same time as BTC data
|
||||
this.fiatData = this.fiatData.filter(d => d[0] >= startFiat);
|
||||
}
|
||||
this.data.push(
|
||||
{value: [now, this.stats.funded_txo_sum - this.stats.spent_txo_sum], symbol: 'none', tooltip: { show: false }}
|
||||
);
|
||||
|
||||
const maxValue = this.data.reduce((acc, d) => Math.max(acc, Math.abs(d[1] ?? d.value[1])), 0);
|
||||
const minValue = this.data.reduce((acc, d) => Math.min(acc, Math.abs(d[1] ?? d.value[1])), maxValue);
|
||||
@ -130,14 +184,42 @@ export class AddressGraphComponent implements OnChanges, OnDestroy {
|
||||
{ offset: 0, color: '#FDD835' },
|
||||
{ offset: 1, color: '#FB8C00' },
|
||||
]),
|
||||
new echarts.graphic.LinearGradient(0, 0, 0, 1, [
|
||||
{ offset: 0, color: '#4CAF50' },
|
||||
{ offset: 1, color: '#1B5E20' },
|
||||
]),
|
||||
],
|
||||
animation: false,
|
||||
grid: {
|
||||
top: 20,
|
||||
bottom: 20,
|
||||
bottom: this.allowZoom ? 65 : 20,
|
||||
right: this.right,
|
||||
left: this.left,
|
||||
},
|
||||
legend: {
|
||||
data: [
|
||||
{
|
||||
name: $localize`:@@7e69426bd97a606d8ae6026762858e6e7c86a1fd:Balance`,
|
||||
inactiveColor: 'var(--grey)',
|
||||
textStyle: {
|
||||
color: 'white',
|
||||
},
|
||||
icon: 'roundRect',
|
||||
},
|
||||
{
|
||||
name: 'Fiat',
|
||||
inactiveColor: 'var(--grey)',
|
||||
textStyle: {
|
||||
color: 'white',
|
||||
},
|
||||
icon: 'roundRect',
|
||||
}
|
||||
],
|
||||
selected: this.selected,
|
||||
formatter: function (name) {
|
||||
return name === 'Fiat' ? 'USD' : 'BTC';
|
||||
}
|
||||
},
|
||||
tooltip: {
|
||||
show: !this.isMobile(),
|
||||
trigger: 'axis',
|
||||
@ -152,27 +234,64 @@ export class AddressGraphComponent implements OnChanges, OnDestroy {
|
||||
align: 'left',
|
||||
},
|
||||
borderColor: '#000',
|
||||
formatter: function (data): string {
|
||||
if (!data?.length || !data[0]?.data?.[2]?.txid) {
|
||||
formatter: function (data) {
|
||||
const btcData = data.filter(d => d.seriesName !== 'Fiat');
|
||||
const fiatData = data.filter(d => d.seriesName === 'Fiat');
|
||||
data = btcData.length ? btcData : fiatData;
|
||||
if ((!btcData.length || !btcData[0]?.data?.[2]?.txid) && !fiatData.length) {
|
||||
return '';
|
||||
}
|
||||
const header = data.length === 1
|
||||
let tooltip = '<div>';
|
||||
|
||||
const hasTx = data[0].data[2].txid;
|
||||
if (hasTx) {
|
||||
const header = data.length === 1
|
||||
? `${data[0].data[2].txid.slice(0, 6)}...${data[0].data[2].txid.slice(-6)}`
|
||||
: `${data.length} transactions`;
|
||||
tooltip += `<span><b>${header}</b></span>`;
|
||||
}
|
||||
|
||||
const date = new Date(data[0].data[0]).toLocaleTimeString(this.locale, { year: 'numeric', month: 'short', day: 'numeric' });
|
||||
const val = data.reduce((total, d) => total + d.data[2].value, 0);
|
||||
const color = val === 0 ? '' : (val > 0 ? 'var(--green)' : 'var(--red)');
|
||||
const symbol = val > 0 ? '+' : '';
|
||||
return `
|
||||
<div>
|
||||
<span><b>${header}</b></span>
|
||||
<div style="text-align: right;">
|
||||
<span style="color: ${color}">${symbol} ${(val / 100_000_000).toFixed(8)} BTC</span><br>
|
||||
<span>${(data[0].data[1] / 100_000_000).toFixed(8)} BTC</span>
|
||||
</div>
|
||||
<span>${date}</span>
|
||||
|
||||
tooltip += `<div>
|
||||
<div style="text-align: right;">`;
|
||||
|
||||
const formatBTC = (val, decimal) => (val / 100_000_000).toFixed(decimal);
|
||||
const formatFiat = (val) => this.fiatCurrencyPipe.transform(val, null, 'USD');
|
||||
|
||||
const btcVal = btcData.reduce((total, d) => total + d.data[2].value, 0);
|
||||
const fiatVal = fiatData.reduce((total, d) => total + d.data[2].value * d.data[2].price / 100_000_000, 0);
|
||||
const btcColor = btcVal === 0 ? '' : (btcVal > 0 ? 'var(--green)' : 'var(--red)');
|
||||
const fiatColor = fiatVal === 0 ? '' : (fiatVal > 0 ? 'var(--green)' : 'var(--red)');
|
||||
const btcSymbol = btcVal > 0 ? '+' : '';
|
||||
const fiatSymbol = fiatVal > 0 ? '+' : '';
|
||||
|
||||
if (btcData.length && fiatData.length) {
|
||||
tooltip += `<div style="display: flex; justify-content: space-between; color: ${btcColor}">
|
||||
<span style="text-align: left; margin-right: 10px;">${btcSymbol} ${formatBTC(btcVal, 4)} BTC</span>
|
||||
<span style="text-align: right;">${fiatSymbol} ${formatFiat(fiatVal)}</span>
|
||||
</div>
|
||||
`;
|
||||
<div style="display: flex; justify-content: space-between;">
|
||||
<span style="text-align: left; margin-right: 10px;">${formatBTC(btcData[0].data[1], 4)} BTC</span>
|
||||
<span style="text-align: right;">${formatFiat(fiatData[0].data[1])}</span>
|
||||
</div>`;
|
||||
} else if (btcData.length) {
|
||||
tooltip += `<span style="color: ${btcColor}">${btcSymbol} ${formatBTC(btcVal, 8)} BTC</span><br>
|
||||
<span>${formatBTC(data[0].data[1], 8)} BTC</span>`;
|
||||
} else {
|
||||
if (this.selected[$localize`:@@7e69426bd97a606d8ae6026762858e6e7c86a1fd:Balance`]) {
|
||||
tooltip += `<div style="display: flex; justify-content: space-between;">
|
||||
<span style="text-align: left; margin-right: 10px;">${formatBTC(data[0].data[3], 4)} BTC</span>
|
||||
<span style="text-align: right;">${formatFiat(data[0].data[1])}</span>
|
||||
</div>`;
|
||||
} else {
|
||||
tooltip += `${hasTx ? `<span style="color: ${fiatColor}">${fiatSymbol} ${formatFiat(fiatVal)}</span><br>` : ''}
|
||||
<span>${formatFiat(data[0].data[1])}</span>`;
|
||||
}
|
||||
}
|
||||
|
||||
tooltip += `</div><span>${date}</span></div>`;
|
||||
return tooltip;
|
||||
}.bind(this)
|
||||
},
|
||||
xAxis: {
|
||||
@ -211,10 +330,24 @@ export class AddressGraphComponent implements OnChanges, OnDestroy {
|
||||
},
|
||||
min: this.period === 'all' ? 0 : 'dataMin'
|
||||
},
|
||||
{
|
||||
type: 'value',
|
||||
axisLabel: {
|
||||
color: 'rgb(110, 112, 121)',
|
||||
formatter: function(val) {
|
||||
return this.fiatShortenerPipe.transform(val, null, 'USD');
|
||||
}.bind(this)
|
||||
},
|
||||
splitLine: {
|
||||
show: false,
|
||||
},
|
||||
min: this.period === 'all' ? 0 : 'dataMin'
|
||||
},
|
||||
],
|
||||
series: [
|
||||
{
|
||||
name: $localize`:@@7e69426bd97a606d8ae6026762858e6e7c86a1fd:Balance`,
|
||||
yAxisIndex: 0,
|
||||
showSymbol: false,
|
||||
symbol: 'circle',
|
||||
symbolSize: 8,
|
||||
@ -226,14 +359,58 @@ export class AddressGraphComponent implements OnChanges, OnDestroy {
|
||||
type: 'line',
|
||||
smooth: false,
|
||||
step: 'end'
|
||||
},
|
||||
{
|
||||
name: 'Fiat',
|
||||
yAxisIndex: 1,
|
||||
showSymbol: false,
|
||||
symbol: 'circle',
|
||||
symbolSize: 8,
|
||||
data: this.fiatData,
|
||||
areaStyle: {
|
||||
opacity: 0.5,
|
||||
},
|
||||
triggerLineEvent: true,
|
||||
type: 'line',
|
||||
smooth: false,
|
||||
step: 'end'
|
||||
}
|
||||
],
|
||||
dataZoom: this.allowZoom ? [{
|
||||
type: 'inside',
|
||||
realtime: true,
|
||||
zoomLock: true,
|
||||
maxSpan: 100,
|
||||
minSpan: 5,
|
||||
moveOnMouseMove: false,
|
||||
}, {
|
||||
showDetail: false,
|
||||
show: true,
|
||||
type: 'slider',
|
||||
brushSelect: false,
|
||||
realtime: true,
|
||||
left: this.left,
|
||||
right: this.right,
|
||||
selectedDataBackground: {
|
||||
lineStyle: {
|
||||
color: '#fff',
|
||||
opacity: 0.45,
|
||||
},
|
||||
},
|
||||
}] : undefined
|
||||
};
|
||||
}
|
||||
|
||||
onChartClick(e) {
|
||||
if (this.hoverData?.length && this.hoverData[0]?.[2]?.txid) {
|
||||
this.router.navigate([this.relativeUrlPipe.transform('/tx/'), this.hoverData[0][2].txid]);
|
||||
this.zone.run(() => {
|
||||
const url = this.relativeUrlPipe.transform(`/tx/${this.hoverData[0][2].txid}`);
|
||||
if (e.event.event.shiftKey || e.event.event.ctrlKey || e.event.event.metaKey) {
|
||||
window.open(url);
|
||||
} else {
|
||||
this.router.navigate([url]);
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
@ -241,10 +418,38 @@ export class AddressGraphComponent implements OnChanges, OnDestroy {
|
||||
this.hoverData = (e?.dataByCoordSys?.[0]?.dataByAxis?.[0]?.seriesDataIndices || []).map(indices => this.data[indices.dataIndex]);
|
||||
}
|
||||
|
||||
onLegendSelectChanged(e) {
|
||||
this.selected = e.selected;
|
||||
this.right = this.selected['Fiat'] ? +this.initialRight + 40 : this.initialRight;
|
||||
this.left = this.selected[$localize`:@@7e69426bd97a606d8ae6026762858e6e7c86a1fd:Balance`] ? this.initialLeft : +this.initialLeft - 40;
|
||||
|
||||
this.chartOptions = {
|
||||
grid: {
|
||||
right: this.right,
|
||||
left: this.left,
|
||||
},
|
||||
legend: {
|
||||
selected: this.selected,
|
||||
},
|
||||
dataZoom: this.allowZoom ? [{
|
||||
left: this.left,
|
||||
right: this.right,
|
||||
}, {
|
||||
left: this.left,
|
||||
right: this.right,
|
||||
}] : undefined
|
||||
};
|
||||
|
||||
if (this.chartInstance) {
|
||||
this.chartInstance.setOption(this.chartOptions);
|
||||
}
|
||||
}
|
||||
|
||||
onChartInit(ec) {
|
||||
this.chartInstance = ec;
|
||||
this.chartInstance.on('showTip', this.onTooltip.bind(this));
|
||||
this.chartInstance.on('click', 'series', this.onChartClick.bind(this));
|
||||
this.chartInstance.on('legendselectchanged', this.onLegendSelectChanged.bind(this));
|
||||
}
|
||||
|
||||
ngOnDestroy(): void {
|
||||
@ -256,4 +461,27 @@ export class AddressGraphComponent implements OnChanges, OnDestroy {
|
||||
isMobile() {
|
||||
return (window.innerWidth <= 767.98);
|
||||
}
|
||||
|
||||
extendSummary(summary) {
|
||||
let extendedSummary = summary.slice();
|
||||
|
||||
// Add a point at today's date to make the graph end at the current time
|
||||
extendedSummary.unshift({ time: Date.now() / 1000, value: 0 });
|
||||
extendedSummary.reverse();
|
||||
|
||||
let oneHour = 60 * 60;
|
||||
// Fill gaps longer than interval
|
||||
for (let i = 0; i < extendedSummary.length - 1; i++) {
|
||||
let hours = Math.floor((extendedSummary[i + 1].time - extendedSummary[i].time) / oneHour);
|
||||
if (hours > 1) {
|
||||
for (let j = 1; j < hours; j++) {
|
||||
let newTime = extendedSummary[i].time + oneHour * j;
|
||||
extendedSummary.splice(i + j, 0, { time: newTime, value: 0 });
|
||||
}
|
||||
i += hours - 1;
|
||||
}
|
||||
}
|
||||
|
||||
return extendedSummary.reverse();
|
||||
}
|
||||
}
|
||||
|
@ -58,7 +58,7 @@ export class AddressTransactionsWidgetComponent implements OnInit, OnChanges, On
|
||||
return summary?.slice(0, 6);
|
||||
}),
|
||||
switchMap(txs => {
|
||||
return (zip(txs.map(tx => this.priceService.getBlockPrice$(tx.time, true, this.currency).pipe(
|
||||
return (zip(txs.map(tx => this.priceService.getBlockPrice$(tx.time, txs.length < 3, this.currency).pipe(
|
||||
map(price => {
|
||||
return {
|
||||
...tx,
|
||||
|
@ -150,7 +150,7 @@ export class TransactionsListComponent implements OnInit, OnChanges {
|
||||
this.transactions.forEach((tx) => {
|
||||
if (!this.blockTime) {
|
||||
if (tx.status.block_time) {
|
||||
this.priceService.getBlockPrice$(tx.status.block_time, confirmedTxs < 10, this.currency).pipe(
|
||||
this.priceService.getBlockPrice$(tx.status.block_time, confirmedTxs < 3, this.currency).pipe(
|
||||
tap((price) => tx['price'] = price),
|
||||
).subscribe();
|
||||
}
|
||||
@ -235,7 +235,7 @@ export class TransactionsListComponent implements OnInit, OnChanges {
|
||||
}
|
||||
|
||||
if (!this.blockTime && tx.status.block_time && this.currency) {
|
||||
this.priceService.getBlockPrice$(tx.status.block_time, confirmedTxs < 10, this.currency).pipe(
|
||||
this.priceService.getBlockPrice$(tx.status.block_time, confirmedTxs < 3, this.currency).pipe(
|
||||
tap((price) => tx['price'] = price),
|
||||
).subscribe();
|
||||
}
|
||||
|
@ -156,6 +156,7 @@ export interface AddressTxSummary {
|
||||
value: number;
|
||||
height: number;
|
||||
time: number;
|
||||
price?: number;
|
||||
}
|
||||
|
||||
export interface ChainStats {
|
||||
|
@ -249,4 +249,75 @@ export class PriceService {
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
getPriceByBulk$(timestamps: number[], currency: string): Observable<Price[]> {
|
||||
if (this.stateService.env.BASE_MODULE !== 'mempool' || !this.stateService.env.HISTORICAL_PRICE) {
|
||||
return of([]);
|
||||
}
|
||||
|
||||
const now = new Date().getTime() / 1000;
|
||||
|
||||
if (!this.priceObservable$ || (this.priceObservable$ && (now - this.lastPriceHistoryUpdate > 3600 || currency !== this.lastQueriedHistoricalCurrency || this.networkChangedSinceLastQuery))) {
|
||||
this.priceObservable$ = this.apiService.getHistoricalPrice$(undefined, currency).pipe(shareReplay());
|
||||
this.lastPriceHistoryUpdate = new Date().getTime() / 1000;
|
||||
this.lastQueriedHistoricalCurrency = currency;
|
||||
this.networkChangedSinceLastQuery = false;
|
||||
}
|
||||
|
||||
return this.priceObservable$.pipe(
|
||||
map((conversion) => {
|
||||
if (!conversion) {
|
||||
return undefined;
|
||||
}
|
||||
|
||||
const historicalPrice = {
|
||||
prices: {},
|
||||
exchangeRates: conversion.exchangeRates,
|
||||
};
|
||||
for (const price of conversion.prices) {
|
||||
historicalPrice.prices[price.time] = this.stateService.env.ADDITIONAL_CURRENCIES ? {
|
||||
USD: price.USD, EUR: price.EUR, GBP: price.GBP, CAD: price.CAD, CHF: price.CHF, AUD: price.AUD,
|
||||
JPY: price.JPY, BGN: price.BGN, BRL: price.BRL, CNY: price.CNY, CZK: price.CZK, DKK: price.DKK,
|
||||
HKD: price.HKD, HRK: price.HRK, HUF: price.HUF, IDR: price.IDR, ILS: price.ILS, INR: price.INR,
|
||||
ISK: price.ISK, KRW: price.KRW, MXN: price.MXN, MYR: price.MYR, NOK: price.NOK, NZD: price.NZD,
|
||||
PHP: price.PHP, PLN: price.PLN, RON: price.RON, RUB: price.RUB, SEK: price.SEK, SGD: price.SGD,
|
||||
THB: price.THB, TRY: price.TRY, ZAR: price.ZAR
|
||||
} : {
|
||||
USD: price.USD, EUR: price.EUR, GBP: price.GBP, CAD: price.CAD, CHF: price.CHF, AUD: price.AUD, JPY: price.JPY
|
||||
};
|
||||
}
|
||||
|
||||
const priceTimestamps = Object.keys(historicalPrice.prices).map(Number);
|
||||
priceTimestamps.push(Number.MAX_SAFE_INTEGER);
|
||||
priceTimestamps.sort((a, b) => b - a);
|
||||
|
||||
const prices: Price[] = [];
|
||||
|
||||
for (const timestamp of timestamps) {
|
||||
let left = 0;
|
||||
let right = priceTimestamps.length - 1;
|
||||
let match = -1;
|
||||
|
||||
// Binary search to find the closest larger element
|
||||
while (left <= right) {
|
||||
const mid = Math.floor((left + right) / 2);
|
||||
if (priceTimestamps[mid] > timestamp) {
|
||||
match = mid;
|
||||
left = mid + 1;
|
||||
} else {
|
||||
right = mid - 1;
|
||||
}
|
||||
}
|
||||
|
||||
if (match !== -1) {
|
||||
const priceTimestamp = priceTimestamps[match];
|
||||
prices.push({
|
||||
price: historicalPrice.prices[priceTimestamp],
|
||||
exchangeRates: historicalPrice.exchangeRates,
|
||||
});
|
||||
}
|
||||
}
|
||||
return prices;
|
||||
}));
|
||||
}
|
||||
}
|
@ -31,7 +31,7 @@ export class FiatShortenerPipe implements PipeTransform {
|
||||
{ value: 1, symbol: '' },
|
||||
{ value: 1e3, symbol: 'k' },
|
||||
{ value: 1e6, symbol: 'M' },
|
||||
{ value: 1e9, symbol: 'G' },
|
||||
{ value: 1e9, symbol: 'B' },
|
||||
{ value: 1e12, symbol: 'T' },
|
||||
{ value: 1e15, symbol: 'P' },
|
||||
{ value: 1e18, symbol: 'E' }
|
||||
|
Loading…
Reference in New Issue
Block a user