Add USD serie in block fee/reward charts
This commit is contained in:
parent
47ad5fffc8
commit
80b3b91a82
@ -17,6 +17,9 @@ import { prepareBlock } from '../utils/blocks-utils';
|
|||||||
import BlocksRepository from '../repositories/BlocksRepository';
|
import BlocksRepository from '../repositories/BlocksRepository';
|
||||||
import HashratesRepository from '../repositories/HashratesRepository';
|
import HashratesRepository from '../repositories/HashratesRepository';
|
||||||
import indexer from '../indexer';
|
import indexer from '../indexer';
|
||||||
|
import fiatConversion from './fiat-conversion';
|
||||||
|
import RatesRepository from '../repositories/RatesRepository';
|
||||||
|
import database from '../database';
|
||||||
import poolsParser from './pools-parser';
|
import poolsParser from './pools-parser';
|
||||||
import BlocksSummariesRepository from '../repositories/BlocksSummariesRepository';
|
import BlocksSummariesRepository from '../repositories/BlocksSummariesRepository';
|
||||||
import mining from './mining/mining';
|
import mining from './mining/mining';
|
||||||
@ -150,6 +153,7 @@ class Blocks {
|
|||||||
blockExtended.extras.reward = transactions[0].vout.reduce((acc, curr) => acc + curr.value, 0);
|
blockExtended.extras.reward = transactions[0].vout.reduce((acc, curr) => acc + curr.value, 0);
|
||||||
blockExtended.extras.coinbaseTx = transactionUtils.stripCoinbaseTransaction(transactions[0]);
|
blockExtended.extras.coinbaseTx = transactionUtils.stripCoinbaseTransaction(transactions[0]);
|
||||||
blockExtended.extras.coinbaseRaw = blockExtended.extras.coinbaseTx.vin[0].scriptsig;
|
blockExtended.extras.coinbaseRaw = blockExtended.extras.coinbaseTx.vin[0].scriptsig;
|
||||||
|
blockExtended.extras.usd = fiatConversion.getConversionRates().USD;
|
||||||
|
|
||||||
if (block.height === 0) {
|
if (block.height === 0) {
|
||||||
blockExtended.extras.medianFee = 0; // 50th percentiles
|
blockExtended.extras.medianFee = 0; // 50th percentiles
|
||||||
|
@ -31,7 +31,7 @@ class Mining {
|
|||||||
*/
|
*/
|
||||||
public async $getHistoricalBlockFees(interval: string | null = null): Promise<any> {
|
public async $getHistoricalBlockFees(interval: string | null = null): Promise<any> {
|
||||||
return await BlocksRepository.$getHistoricalBlockFees(
|
return await BlocksRepository.$getHistoricalBlockFees(
|
||||||
this.getTimeRange(interval),
|
this.getTimeRangeForAmounts(interval),
|
||||||
Common.getSqlInterval(interval)
|
Common.getSqlInterval(interval)
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
@ -41,7 +41,7 @@ class Mining {
|
|||||||
*/
|
*/
|
||||||
public async $getHistoricalBlockRewards(interval: string | null = null): Promise<any> {
|
public async $getHistoricalBlockRewards(interval: string | null = null): Promise<any> {
|
||||||
return await BlocksRepository.$getHistoricalBlockRewards(
|
return await BlocksRepository.$getHistoricalBlockRewards(
|
||||||
this.getTimeRange(interval),
|
this.getTimeRangeForAmounts(interval),
|
||||||
Common.getSqlInterval(interval)
|
Common.getSqlInterval(interval)
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
@ -462,6 +462,21 @@ class Mining {
|
|||||||
return date;
|
return date;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private getTimeRangeForAmounts(interval: string | null): number {
|
||||||
|
switch (interval) {
|
||||||
|
case '3y': return 1296000;
|
||||||
|
case '2y': return 864000;
|
||||||
|
case '1y': return 432000;
|
||||||
|
case '6m': return 216000;
|
||||||
|
case '3m': return 108000;
|
||||||
|
case '1m': return 36000;
|
||||||
|
case '1w': return 8400;
|
||||||
|
case '3d': return 3600;
|
||||||
|
case '24h': return 1200;
|
||||||
|
default: return 3888000;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
private getTimeRange(interval: string | null): number {
|
private getTimeRange(interval: string | null): number {
|
||||||
switch (interval) {
|
switch (interval) {
|
||||||
case '3y': return 43200; // 12h
|
case '3y': return 43200; // 12h
|
||||||
@ -473,7 +488,7 @@ class Mining {
|
|||||||
case '1w': return 300; // 5min
|
case '1w': return 300; // 5min
|
||||||
case '3d': return 1;
|
case '3d': return 1;
|
||||||
case '24h': return 1;
|
case '24h': return 1;
|
||||||
default: return 86400; // 24h
|
default: return 86400;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -109,6 +109,7 @@ export interface BlockExtension {
|
|||||||
avgFee?: number;
|
avgFee?: number;
|
||||||
avgFeeRate?: number;
|
avgFeeRate?: number;
|
||||||
coinbaseRaw?: string;
|
coinbaseRaw?: string;
|
||||||
|
usd?: number | null;
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface BlockExtended extends IEsploraApi.Block {
|
export interface BlockExtended extends IEsploraApi.Block {
|
||||||
|
@ -256,7 +256,7 @@ class BlocksRepository {
|
|||||||
|
|
||||||
const params: any[] = [];
|
const params: any[] = [];
|
||||||
let query = ` SELECT
|
let query = ` SELECT
|
||||||
height,
|
blocks.height,
|
||||||
hash as id,
|
hash as id,
|
||||||
UNIX_TIMESTAMP(blocks.blockTimestamp) as blockTimestamp,
|
UNIX_TIMESTAMP(blocks.blockTimestamp) as blockTimestamp,
|
||||||
size,
|
size,
|
||||||
@ -274,8 +274,10 @@ class BlocksRepository {
|
|||||||
merkle_root,
|
merkle_root,
|
||||||
previous_block_hash as previousblockhash,
|
previous_block_hash as previousblockhash,
|
||||||
avg_fee,
|
avg_fee,
|
||||||
avg_fee_rate
|
avg_fee_rate,
|
||||||
|
IFNULL(JSON_EXTRACT(rates.bisq_rates, '$.USD'), null) as usd
|
||||||
FROM blocks
|
FROM blocks
|
||||||
|
LEFT JOIN rates on rates.height = blocks.height
|
||||||
WHERE pool_id = ?`;
|
WHERE pool_id = ?`;
|
||||||
params.push(pool.id);
|
params.push(pool.id);
|
||||||
|
|
||||||
@ -308,7 +310,7 @@ class BlocksRepository {
|
|||||||
public async $getBlockByHeight(height: number): Promise<object | null> {
|
public async $getBlockByHeight(height: number): Promise<object | null> {
|
||||||
try {
|
try {
|
||||||
const [rows]: any[] = await DB.query(`SELECT
|
const [rows]: any[] = await DB.query(`SELECT
|
||||||
height,
|
blocks.height,
|
||||||
hash,
|
hash,
|
||||||
hash as id,
|
hash as id,
|
||||||
UNIX_TIMESTAMP(blocks.blockTimestamp) as blockTimestamp,
|
UNIX_TIMESTAMP(blocks.blockTimestamp) as blockTimestamp,
|
||||||
@ -333,10 +335,12 @@ class BlocksRepository {
|
|||||||
merkle_root,
|
merkle_root,
|
||||||
previous_block_hash as previousblockhash,
|
previous_block_hash as previousblockhash,
|
||||||
avg_fee,
|
avg_fee,
|
||||||
avg_fee_rate
|
avg_fee_rate,
|
||||||
|
IFNULL(JSON_EXTRACT(rates.bisq_rates, '$.USD'), null) as usd
|
||||||
FROM blocks
|
FROM blocks
|
||||||
JOIN pools ON blocks.pool_id = pools.id
|
JOIN pools ON blocks.pool_id = pools.id
|
||||||
WHERE height = ${height};
|
LEFT JOIN rates on rates.height = blocks.height
|
||||||
|
WHERE blocks.height = ${height};
|
||||||
`);
|
`);
|
||||||
|
|
||||||
if (rows.length <= 0) {
|
if (rows.length <= 0) {
|
||||||
@ -357,12 +361,14 @@ class BlocksRepository {
|
|||||||
public async $getBlockByHash(hash: string): Promise<object | null> {
|
public async $getBlockByHash(hash: string): Promise<object | null> {
|
||||||
try {
|
try {
|
||||||
const query = `
|
const query = `
|
||||||
SELECT *, UNIX_TIMESTAMP(blocks.blockTimestamp) as blockTimestamp, hash as id,
|
SELECT *, blocks.height, UNIX_TIMESTAMP(blocks.blockTimestamp) as blockTimestamp, hash as id,
|
||||||
pools.id as pool_id, pools.name as pool_name, pools.link as pool_link, pools.slug as pool_slug,
|
pools.id as pool_id, pools.name as pool_name, pools.link as pool_link, pools.slug as pool_slug,
|
||||||
pools.addresses as pool_addresses, pools.regexes as pool_regexes,
|
pools.addresses as pool_addresses, pools.regexes as pool_regexes,
|
||||||
previous_block_hash as previousblockhash
|
previous_block_hash as previousblockhash,
|
||||||
|
IFNULL(JSON_EXTRACT(rates.bisq_rates, '$.USD'), null) as usd
|
||||||
FROM blocks
|
FROM blocks
|
||||||
JOIN pools ON blocks.pool_id = pools.id
|
JOIN pools ON blocks.pool_id = pools.id
|
||||||
|
LEFT JOIN rates on rates.height = blocks.height
|
||||||
WHERE hash = '${hash}';
|
WHERE hash = '${hash}';
|
||||||
`;
|
`;
|
||||||
const [rows]: any[] = await DB.query(query);
|
const [rows]: any[] = await DB.query(query);
|
||||||
@ -473,10 +479,12 @@ class BlocksRepository {
|
|||||||
public async $getHistoricalBlockFees(div: number, interval: string | null): Promise<any> {
|
public async $getHistoricalBlockFees(div: number, interval: string | null): Promise<any> {
|
||||||
try {
|
try {
|
||||||
let query = `SELECT
|
let query = `SELECT
|
||||||
CAST(AVG(height) as INT) as avgHeight,
|
CAST(AVG(blocks.height) as INT) as avgHeight,
|
||||||
CAST(AVG(UNIX_TIMESTAMP(blockTimestamp)) as INT) as timestamp,
|
CAST(AVG(UNIX_TIMESTAMP(blockTimestamp)) as INT) as timestamp,
|
||||||
CAST(AVG(fees) as INT) as avgFees
|
CAST(AVG(fees) as INT) as avgFees,
|
||||||
FROM blocks`;
|
IFNULL(JSON_EXTRACT(rates.bisq_rates, '$.USD'), null) as usd
|
||||||
|
FROM blocks
|
||||||
|
LEFT JOIN rates on rates.height = blocks.height`;
|
||||||
|
|
||||||
if (interval !== null) {
|
if (interval !== null) {
|
||||||
query += ` WHERE blockTimestamp BETWEEN DATE_SUB(NOW(), INTERVAL ${interval}) AND NOW()`;
|
query += ` WHERE blockTimestamp BETWEEN DATE_SUB(NOW(), INTERVAL ${interval}) AND NOW()`;
|
||||||
@ -498,10 +506,12 @@ class BlocksRepository {
|
|||||||
public async $getHistoricalBlockRewards(div: number, interval: string | null): Promise<any> {
|
public async $getHistoricalBlockRewards(div: number, interval: string | null): Promise<any> {
|
||||||
try {
|
try {
|
||||||
let query = `SELECT
|
let query = `SELECT
|
||||||
CAST(AVG(height) as INT) as avgHeight,
|
CAST(AVG(blocks.height) as INT) as avgHeight,
|
||||||
CAST(AVG(UNIX_TIMESTAMP(blockTimestamp)) as INT) as timestamp,
|
CAST(AVG(UNIX_TIMESTAMP(blockTimestamp)) as INT) as timestamp,
|
||||||
CAST(AVG(reward) as INT) as avgRewards
|
CAST(AVG(reward) as INT) as avgRewards,
|
||||||
FROM blocks`;
|
IFNULL(JSON_EXTRACT(rates.bisq_rates, '$.USD'), null) as usd
|
||||||
|
FROM blocks
|
||||||
|
LEFT JOIN rates on rates.height = blocks.height`;
|
||||||
|
|
||||||
if (interval !== null) {
|
if (interval !== null) {
|
||||||
query += ` WHERE blockTimestamp BETWEEN DATE_SUB(NOW(), INTERVAL ${interval}) AND NOW()`;
|
query += ` WHERE blockTimestamp BETWEEN DATE_SUB(NOW(), INTERVAL ${interval}) AND NOW()`;
|
||||||
|
@ -27,6 +27,7 @@ export function prepareBlock(block: any): BlockExtended {
|
|||||||
name: block.pool_name,
|
name: block.pool_name,
|
||||||
slug: block.pool_slug,
|
slug: block.pool_slug,
|
||||||
} : undefined),
|
} : undefined),
|
||||||
|
usd: block?.extras?.usd ?? block.usd ?? null,
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
@ -13,6 +13,7 @@ import { SharedModule } from './shared/shared.module';
|
|||||||
import { StorageService } from './services/storage.service';
|
import { StorageService } from './services/storage.service';
|
||||||
import { HttpCacheInterceptor } from './services/http-cache.interceptor';
|
import { HttpCacheInterceptor } from './services/http-cache.interceptor';
|
||||||
import { LanguageService } from './services/language.service';
|
import { LanguageService } from './services/language.service';
|
||||||
|
import { FiatShortenerPipe } from './shared/pipes/fiat-shortener.pipe';
|
||||||
import { ShortenStringPipe } from './shared/pipes/shorten-string-pipe/shorten-string.pipe';
|
import { ShortenStringPipe } from './shared/pipes/shorten-string-pipe/shorten-string.pipe';
|
||||||
import { CapAddressPipe } from './shared/pipes/cap-address-pipe/cap-address-pipe';
|
import { CapAddressPipe } from './shared/pipes/cap-address-pipe/cap-address-pipe';
|
||||||
|
|
||||||
@ -37,6 +38,7 @@ import { CapAddressPipe } from './shared/pipes/cap-address-pipe/cap-address-pipe
|
|||||||
StorageService,
|
StorageService,
|
||||||
LanguageService,
|
LanguageService,
|
||||||
ShortenStringPipe,
|
ShortenStringPipe,
|
||||||
|
FiatShortenerPipe,
|
||||||
CapAddressPipe,
|
CapAddressPipe,
|
||||||
{ provide: HTTP_INTERCEPTORS, useClass: HttpCacheInterceptor, multi: true }
|
{ provide: HTTP_INTERCEPTORS, useClass: HttpCacheInterceptor, multi: true }
|
||||||
],
|
],
|
||||||
|
@ -180,8 +180,8 @@ export class BlockFeeRatesGraphComponent implements OnInit {
|
|||||||
}
|
}
|
||||||
let tooltip = `<b style="color: white; margin-left: 2px">${formatterXAxis(this.locale, this.timespan, parseInt(data[0].axisValue, 10))}</b><br>`;
|
let tooltip = `<b style="color: white; margin-left: 2px">${formatterXAxis(this.locale, this.timespan, parseInt(data[0].axisValue, 10))}</b><br>`;
|
||||||
|
|
||||||
for (const pool of data.reverse()) {
|
for (const rate of data.reverse()) {
|
||||||
tooltip += `${pool.marker} ${pool.seriesName}: ${pool.data[1]} sats/vByte<br>`;
|
tooltip += `${rate.marker} ${rate.seriesName}: ${rate.data[1]} sats/vByte<br>`;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (['24h', '3d'].includes(this.timespan)) {
|
if (['24h', '3d'].includes(this.timespan)) {
|
||||||
|
@ -4,12 +4,13 @@ import { Observable } from 'rxjs';
|
|||||||
import { map, share, startWith, switchMap, tap } from 'rxjs/operators';
|
import { map, share, startWith, switchMap, tap } from 'rxjs/operators';
|
||||||
import { ApiService } from 'src/app/services/api.service';
|
import { ApiService } from 'src/app/services/api.service';
|
||||||
import { SeoService } from 'src/app/services/seo.service';
|
import { SeoService } from 'src/app/services/seo.service';
|
||||||
import { formatNumber } from '@angular/common';
|
import { formatCurrency, formatNumber, getCurrencySymbol } from '@angular/common';
|
||||||
import { FormBuilder, FormGroup } from '@angular/forms';
|
import { FormBuilder, FormGroup } from '@angular/forms';
|
||||||
import { download, formatterXAxis, formatterXAxisLabel } from 'src/app/shared/graphs.utils';
|
import { download, formatterXAxis, formatterXAxisLabel, formatterXAxisTimeCategory } from 'src/app/shared/graphs.utils';
|
||||||
import { StorageService } from 'src/app/services/storage.service';
|
import { StorageService } from 'src/app/services/storage.service';
|
||||||
import { MiningService } from 'src/app/services/mining.service';
|
import { MiningService } from 'src/app/services/mining.service';
|
||||||
import { ActivatedRoute } from '@angular/router';
|
import { ActivatedRoute } from '@angular/router';
|
||||||
|
import { FiatShortenerPipe } from 'src/app/shared/pipes/fiat-shortener.pipe';
|
||||||
|
|
||||||
@Component({
|
@Component({
|
||||||
selector: 'app-block-fees-graph',
|
selector: 'app-block-fees-graph',
|
||||||
@ -51,6 +52,7 @@ export class BlockFeesGraphComponent implements OnInit {
|
|||||||
private storageService: StorageService,
|
private storageService: StorageService,
|
||||||
private miningService: MiningService,
|
private miningService: MiningService,
|
||||||
private route: ActivatedRoute,
|
private route: ActivatedRoute,
|
||||||
|
private fiatShortenerPipe: FiatShortenerPipe,
|
||||||
) {
|
) {
|
||||||
this.radioGroupForm = this.formBuilder.group({ dateSpan: '1y' });
|
this.radioGroupForm = this.formBuilder.group({ dateSpan: '1y' });
|
||||||
this.radioGroupForm.controls.dateSpan.setValue('1y');
|
this.radioGroupForm.controls.dateSpan.setValue('1y');
|
||||||
@ -82,6 +84,7 @@ export class BlockFeesGraphComponent implements OnInit {
|
|||||||
tap((response) => {
|
tap((response) => {
|
||||||
this.prepareChartOptions({
|
this.prepareChartOptions({
|
||||||
blockFees: response.body.map(val => [val.timestamp * 1000, val.avgFees / 100000000, val.avgHeight]),
|
blockFees: response.body.map(val => [val.timestamp * 1000, val.avgFees / 100000000, val.avgHeight]),
|
||||||
|
blockFeesUSD: response.body.filter(val => val.usd > 0).map(val => [val.timestamp * 1000, val.avgFees / 100000000 * val.usd, val.avgHeight]),
|
||||||
});
|
});
|
||||||
this.isLoading = false;
|
this.isLoading = false;
|
||||||
}),
|
}),
|
||||||
@ -98,16 +101,17 @@ export class BlockFeesGraphComponent implements OnInit {
|
|||||||
|
|
||||||
prepareChartOptions(data) {
|
prepareChartOptions(data) {
|
||||||
this.chartOptions = {
|
this.chartOptions = {
|
||||||
animation: false,
|
|
||||||
color: [
|
color: [
|
||||||
new graphic.LinearGradient(0, 0, 0, 0.65, [
|
new graphic.LinearGradient(0, 0, 0, 1, [
|
||||||
{ offset: 0, color: '#F4511E' },
|
{ offset: 0, color: '#00ACC1' },
|
||||||
{ offset: 0.25, color: '#FB8C00' },
|
{ offset: 1, color: '#0D47A1' },
|
||||||
{ offset: 0.5, color: '#FFB300' },
|
]),
|
||||||
{ offset: 0.75, color: '#FDD835' },
|
new graphic.LinearGradient(0, 0, 0, 1, [
|
||||||
{ offset: 1, color: '#7CB342' }
|
{ offset: 0, color: '#FDD835' },
|
||||||
|
{ offset: 1, color: '#FB8C00' },
|
||||||
]),
|
]),
|
||||||
],
|
],
|
||||||
|
animation: false,
|
||||||
grid: {
|
grid: {
|
||||||
top: 30,
|
top: 30,
|
||||||
bottom: 80,
|
bottom: 80,
|
||||||
@ -128,28 +132,52 @@ export class BlockFeesGraphComponent implements OnInit {
|
|||||||
align: 'left',
|
align: 'left',
|
||||||
},
|
},
|
||||||
borderColor: '#000',
|
borderColor: '#000',
|
||||||
formatter: (ticks) => {
|
formatter: function (data) {
|
||||||
let tooltip = `<b style="color: white; margin-left: 2px">${formatterXAxis(this.locale, this.timespan, parseInt(ticks[0].axisValue, 10))}</b><br>`;
|
if (data.length <= 0) {
|
||||||
tooltip += `${ticks[0].marker} ${ticks[0].seriesName}: ${formatNumber(ticks[0].data[1], this.locale, '1.3-3')} BTC`;
|
return '';
|
||||||
tooltip += `<br>`;
|
}
|
||||||
|
let tooltip = `<b style="color: white; margin-left: 2px">
|
||||||
|
${formatterXAxis(this.locale, this.timespan, parseInt(data[0].axisValue, 10))}</b><br>`;
|
||||||
|
|
||||||
if (['24h', '3d'].includes(this.timespan)) {
|
for (const tick of data) {
|
||||||
tooltip += `<small>` + $localize`At block: ${ticks[0].data[2]}` + `</small>`;
|
if (tick.seriesIndex === 0) {
|
||||||
} else {
|
tooltip += `${tick.marker} ${tick.seriesName}: ${formatNumber(tick.data[1], this.locale, '1.3-3')} BTC<br>`;
|
||||||
tooltip += `<small>` + $localize`Around block: ${ticks[0].data[2]}` + `</small>`;
|
} else if (tick.seriesIndex === 1) {
|
||||||
|
tooltip += `${tick.marker} ${tick.seriesName}: ${formatCurrency(tick.data[1], this.locale, getCurrencySymbol('USD', 'narrow'), 'USD', '1.0-0')}<br>`;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
tooltip += `<small>* On average around block ${data[0].data[2]}</small>`;
|
||||||
return tooltip;
|
return tooltip;
|
||||||
}
|
}.bind(this)
|
||||||
},
|
},
|
||||||
xAxis: {
|
xAxis: data.blockFees.length === 0 ? undefined :
|
||||||
name: formatterXAxisLabel(this.locale, this.timespan),
|
{
|
||||||
nameLocation: 'middle',
|
|
||||||
nameTextStyle: {
|
|
||||||
padding: [10, 0, 0, 0],
|
|
||||||
},
|
|
||||||
type: 'time',
|
type: 'time',
|
||||||
splitNumber: this.isMobile() ? 5 : 10,
|
splitNumber: this.isMobile() ? 5 : 10,
|
||||||
|
axisLabel: {
|
||||||
|
hideOverlap: true,
|
||||||
|
}
|
||||||
|
},
|
||||||
|
legend: {
|
||||||
|
data: [
|
||||||
|
{
|
||||||
|
name: 'Fees BTC',
|
||||||
|
inactiveColor: 'rgb(110, 112, 121)',
|
||||||
|
textStyle: {
|
||||||
|
color: 'white',
|
||||||
|
},
|
||||||
|
icon: 'roundRect',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: 'Fees USD',
|
||||||
|
inactiveColor: 'rgb(110, 112, 121)',
|
||||||
|
textStyle: {
|
||||||
|
color: 'white',
|
||||||
|
},
|
||||||
|
icon: 'roundRect',
|
||||||
|
},
|
||||||
|
],
|
||||||
},
|
},
|
||||||
yAxis: [
|
yAxis: [
|
||||||
{
|
{
|
||||||
@ -160,6 +188,9 @@ export class BlockFeesGraphComponent implements OnInit {
|
|||||||
return `${val} BTC`;
|
return `${val} BTC`;
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
max: (value) => {
|
||||||
|
return Math.floor(value.max * 2 * 10) / 10;
|
||||||
|
},
|
||||||
splitLine: {
|
splitLine: {
|
||||||
lineStyle: {
|
lineStyle: {
|
||||||
type: 'dotted',
|
type: 'dotted',
|
||||||
@ -168,18 +199,47 @@ export class BlockFeesGraphComponent implements OnInit {
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
type: 'value',
|
||||||
|
position: 'right',
|
||||||
|
axisLabel: {
|
||||||
|
color: 'rgb(110, 112, 121)',
|
||||||
|
formatter: function(val) {
|
||||||
|
return this.fiatShortenerPipe.transform(val);
|
||||||
|
}.bind(this)
|
||||||
|
},
|
||||||
|
splitLine: {
|
||||||
|
show: false,
|
||||||
|
},
|
||||||
|
},
|
||||||
],
|
],
|
||||||
series: [
|
series: [
|
||||||
{
|
{
|
||||||
|
legendHoverLink: false,
|
||||||
zlevel: 0,
|
zlevel: 0,
|
||||||
name: $localize`:@@c20172223f84462032664d717d739297e5a9e2fe:Fees`,
|
yAxisIndex: 0,
|
||||||
showSymbol: false,
|
name: 'Fees BTC',
|
||||||
symbol: 'none',
|
|
||||||
data: data.blockFees,
|
data: data.blockFees,
|
||||||
type: 'line',
|
type: 'line',
|
||||||
|
smooth: 0.25,
|
||||||
|
symbol: 'none',
|
||||||
|
areaStyle: {
|
||||||
|
opacity: 0.25,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
legendHoverLink: false,
|
||||||
|
zlevel: 1,
|
||||||
|
yAxisIndex: 1,
|
||||||
|
name: 'Fees USD',
|
||||||
|
data: data.blockFeesUSD,
|
||||||
|
type: 'line',
|
||||||
|
smooth: 0.25,
|
||||||
|
symbol: 'none',
|
||||||
lineStyle: {
|
lineStyle: {
|
||||||
width: 2,
|
width: 2,
|
||||||
},
|
opacity: 0.75,
|
||||||
|
}
|
||||||
},
|
},
|
||||||
],
|
],
|
||||||
dataZoom: [{
|
dataZoom: [{
|
||||||
|
@ -4,12 +4,13 @@ import { Observable } from 'rxjs';
|
|||||||
import { map, share, startWith, switchMap, tap } from 'rxjs/operators';
|
import { map, share, startWith, switchMap, tap } from 'rxjs/operators';
|
||||||
import { ApiService } from 'src/app/services/api.service';
|
import { ApiService } from 'src/app/services/api.service';
|
||||||
import { SeoService } from 'src/app/services/seo.service';
|
import { SeoService } from 'src/app/services/seo.service';
|
||||||
import { formatNumber } from '@angular/common';
|
import { formatCurrency, formatNumber, getCurrencySymbol } from '@angular/common';
|
||||||
import { FormBuilder, FormGroup } from '@angular/forms';
|
import { FormBuilder, FormGroup } from '@angular/forms';
|
||||||
import { download, formatterXAxis, formatterXAxisLabel } from 'src/app/shared/graphs.utils';
|
import { download, formatterXAxis, formatterXAxisLabel, formatterXAxisTimeCategory } from 'src/app/shared/graphs.utils';
|
||||||
import { MiningService } from 'src/app/services/mining.service';
|
import { MiningService } from 'src/app/services/mining.service';
|
||||||
import { StorageService } from 'src/app/services/storage.service';
|
import { StorageService } from 'src/app/services/storage.service';
|
||||||
import { ActivatedRoute } from '@angular/router';
|
import { ActivatedRoute } from '@angular/router';
|
||||||
|
import { FiatShortenerPipe } from 'src/app/shared/pipes/fiat-shortener.pipe';
|
||||||
|
|
||||||
@Component({
|
@Component({
|
||||||
selector: 'app-block-rewards-graph',
|
selector: 'app-block-rewards-graph',
|
||||||
@ -51,6 +52,7 @@ export class BlockRewardsGraphComponent implements OnInit {
|
|||||||
private miningService: MiningService,
|
private miningService: MiningService,
|
||||||
private storageService: StorageService,
|
private storageService: StorageService,
|
||||||
private route: ActivatedRoute,
|
private route: ActivatedRoute,
|
||||||
|
private fiatShortenerPipe: FiatShortenerPipe,
|
||||||
) {
|
) {
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -80,6 +82,7 @@ export class BlockRewardsGraphComponent implements OnInit {
|
|||||||
tap((response) => {
|
tap((response) => {
|
||||||
this.prepareChartOptions({
|
this.prepareChartOptions({
|
||||||
blockRewards: response.body.map(val => [val.timestamp * 1000, val.avgRewards / 100000000, val.avgHeight]),
|
blockRewards: response.body.map(val => [val.timestamp * 1000, val.avgRewards / 100000000, val.avgHeight]),
|
||||||
|
blockRewardsUSD: response.body.filter(val => val.usd > 0).map(val => [val.timestamp * 1000, val.avgRewards / 100000000 * val.usd, val.avgHeight]),
|
||||||
});
|
});
|
||||||
this.isLoading = false;
|
this.isLoading = false;
|
||||||
}),
|
}),
|
||||||
@ -95,15 +98,18 @@ export class BlockRewardsGraphComponent implements OnInit {
|
|||||||
}
|
}
|
||||||
|
|
||||||
prepareChartOptions(data) {
|
prepareChartOptions(data) {
|
||||||
|
const scaleFactor = 0.1;
|
||||||
|
|
||||||
this.chartOptions = {
|
this.chartOptions = {
|
||||||
animation: false,
|
animation: false,
|
||||||
color: [
|
color: [
|
||||||
new graphic.LinearGradient(0, 0, 0, 0.65, [
|
new graphic.LinearGradient(0, 0, 0, 1, [
|
||||||
{ offset: 0, color: '#F4511E' },
|
{ offset: 0, color: '#00ACC1' },
|
||||||
{ offset: 0.25, color: '#FB8C00' },
|
{ offset: 1, color: '#0D47A1' },
|
||||||
{ offset: 0.5, color: '#FFB300' },
|
]),
|
||||||
{ offset: 0.75, color: '#FDD835' },
|
new graphic.LinearGradient(0, 0, 0, 1, [
|
||||||
{ offset: 1, color: '#7CB342' }
|
{ offset: 0, color: '#FDD835' },
|
||||||
|
{ offset: 1, color: '#FB8C00' },
|
||||||
]),
|
]),
|
||||||
],
|
],
|
||||||
grid: {
|
grid: {
|
||||||
@ -126,33 +132,55 @@ export class BlockRewardsGraphComponent implements OnInit {
|
|||||||
align: 'left',
|
align: 'left',
|
||||||
},
|
},
|
||||||
borderColor: '#000',
|
borderColor: '#000',
|
||||||
formatter: (ticks) => {
|
formatter: function (data) {
|
||||||
let tooltip = `<b style="color: white; margin-left: 2px">${formatterXAxis(this.locale, this.timespan, parseInt(ticks[0].axisValue, 10))}</b><br>`;
|
if (data.length <= 0) {
|
||||||
tooltip += `${ticks[0].marker} ${ticks[0].seriesName}: ${formatNumber(ticks[0].data[1], this.locale, '1.3-3')} BTC`;
|
return '';
|
||||||
tooltip += `<br>`;
|
}
|
||||||
|
let tooltip = `<b style="color: white; margin-left: 2px">
|
||||||
|
${formatterXAxis(this.locale, this.timespan, parseInt(data[0].axisValue, 10))}</b><br>`;
|
||||||
|
|
||||||
if (['24h', '3d'].includes(this.timespan)) {
|
for (const tick of data) {
|
||||||
tooltip += `<small>` + $localize`At block: ${ticks[0].data[2]}` + `</small>`;
|
if (tick.seriesIndex === 0) {
|
||||||
} else {
|
tooltip += `${tick.marker} ${tick.seriesName}: ${formatNumber(tick.data[1], this.locale, '1.3-3')} BTC<br>`;
|
||||||
tooltip += `<small>` + $localize`Around block: ${ticks[0].data[2]}` + `</small>`;
|
} else if (tick.seriesIndex === 1) {
|
||||||
|
tooltip += `${tick.marker} ${tick.seriesName}: ${formatCurrency(tick.data[1], this.locale, getCurrencySymbol('USD', 'narrow'), 'USD', '1.0-0')}<br>`;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
tooltip += `<small>* On average around block ${data[0].data[2]}</small>`;
|
||||||
return tooltip;
|
return tooltip;
|
||||||
}
|
}.bind(this)
|
||||||
},
|
},
|
||||||
xAxis: {
|
xAxis: data.blockRewards.length === 0 ? undefined :
|
||||||
name: formatterXAxisLabel(this.locale, this.timespan),
|
{
|
||||||
nameLocation: 'middle',
|
|
||||||
nameTextStyle: {
|
|
||||||
padding: [10, 0, 0, 0],
|
|
||||||
},
|
|
||||||
type: 'time',
|
type: 'time',
|
||||||
splitNumber: this.isMobile() ? 5 : 10,
|
splitNumber: this.isMobile() ? 5 : 10,
|
||||||
|
axisLabel: {
|
||||||
|
hideOverlap: true,
|
||||||
|
}
|
||||||
|
},
|
||||||
|
legend: {
|
||||||
|
data: [
|
||||||
|
{
|
||||||
|
name: 'Rewards BTC',
|
||||||
|
inactiveColor: 'rgb(110, 112, 121)',
|
||||||
|
textStyle: {
|
||||||
|
color: 'white',
|
||||||
|
},
|
||||||
|
icon: 'roundRect',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: 'Rewards USD',
|
||||||
|
inactiveColor: 'rgb(110, 112, 121)',
|
||||||
|
textStyle: {
|
||||||
|
color: 'white',
|
||||||
|
},
|
||||||
|
icon: 'roundRect',
|
||||||
|
},
|
||||||
|
],
|
||||||
},
|
},
|
||||||
yAxis: [
|
yAxis: [
|
||||||
{
|
{
|
||||||
min: value => Math.round(10 * value.min * 0.99) / 10,
|
|
||||||
max: value => Math.round(10 * value.max * 1.01) / 10,
|
|
||||||
type: 'value',
|
type: 'value',
|
||||||
axisLabel: {
|
axisLabel: {
|
||||||
color: 'rgb(110, 112, 121)',
|
color: 'rgb(110, 112, 121)',
|
||||||
@ -160,6 +188,12 @@ export class BlockRewardsGraphComponent implements OnInit {
|
|||||||
return `${val} BTC`;
|
return `${val} BTC`;
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
min: (value) => {
|
||||||
|
return Math.round(value.min * (1.0 - scaleFactor) * 10) / 10;
|
||||||
|
},
|
||||||
|
max: (value) => {
|
||||||
|
return Math.round(value.max * (1.0 + scaleFactor) * 10) / 10;
|
||||||
|
},
|
||||||
splitLine: {
|
splitLine: {
|
||||||
lineStyle: {
|
lineStyle: {
|
||||||
type: 'dotted',
|
type: 'dotted',
|
||||||
@ -168,18 +202,53 @@ export class BlockRewardsGraphComponent implements OnInit {
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
min: (value) => {
|
||||||
|
return Math.round(value.min * (1.0 - scaleFactor) * 10) / 10;
|
||||||
|
},
|
||||||
|
max: (value) => {
|
||||||
|
return Math.round(value.max * (1.0 + scaleFactor) * 10) / 10;
|
||||||
|
},
|
||||||
|
type: 'value',
|
||||||
|
position: 'right',
|
||||||
|
axisLabel: {
|
||||||
|
color: 'rgb(110, 112, 121)',
|
||||||
|
formatter: function(val) {
|
||||||
|
return this.fiatShortenerPipe.transform(val);
|
||||||
|
}.bind(this)
|
||||||
|
},
|
||||||
|
splitLine: {
|
||||||
|
show: false,
|
||||||
|
},
|
||||||
|
},
|
||||||
],
|
],
|
||||||
series: [
|
series: [
|
||||||
{
|
{
|
||||||
|
legendHoverLink: false,
|
||||||
zlevel: 0,
|
zlevel: 0,
|
||||||
name: $localize`:@@12f86e6747a5ad39e62d3480ddc472b1aeab5b76:Reward`,
|
yAxisIndex: 0,
|
||||||
showSymbol: false,
|
name: 'Rewards BTC',
|
||||||
symbol: 'none',
|
|
||||||
data: data.blockRewards,
|
data: data.blockRewards,
|
||||||
type: 'line',
|
type: 'line',
|
||||||
|
smooth: 0.25,
|
||||||
|
symbol: 'none',
|
||||||
|
areaStyle: {
|
||||||
|
opacity: 0.25,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
legendHoverLink: false,
|
||||||
|
zlevel: 1,
|
||||||
|
yAxisIndex: 1,
|
||||||
|
name: 'Rewards USD',
|
||||||
|
data: data.blockRewardsUSD,
|
||||||
|
type: 'line',
|
||||||
|
smooth: 0.25,
|
||||||
|
symbol: 'none',
|
||||||
lineStyle: {
|
lineStyle: {
|
||||||
width: 2,
|
width: 2,
|
||||||
},
|
opacity: 0.75,
|
||||||
|
}
|
||||||
},
|
},
|
||||||
],
|
],
|
||||||
dataZoom: [{
|
dataZoom: [{
|
||||||
|
@ -351,6 +351,7 @@ export class HashrateChartComponent implements OnInit {
|
|||||||
series: data.hashrates.length === 0 ? [] : [
|
series: data.hashrates.length === 0 ? [] : [
|
||||||
{
|
{
|
||||||
zlevel: 0,
|
zlevel: 0,
|
||||||
|
yAxisIndex: 0,
|
||||||
name: $localize`:@@79a9dc5b1caca3cbeb1733a19515edacc5fc7920:Hashrate`,
|
name: $localize`:@@79a9dc5b1caca3cbeb1733a19515edacc5fc7920:Hashrate`,
|
||||||
showSymbol: false,
|
showSymbol: false,
|
||||||
symbol: 'none',
|
symbol: 'none',
|
||||||
|
37
frontend/src/app/shared/pipes/fiat-shortener.pipe.ts
Normal file
37
frontend/src/app/shared/pipes/fiat-shortener.pipe.ts
Normal file
@ -0,0 +1,37 @@
|
|||||||
|
import { formatCurrency, getCurrencySymbol } from '@angular/common';
|
||||||
|
import { Inject, LOCALE_ID, Pipe, PipeTransform } from '@angular/core';
|
||||||
|
|
||||||
|
@Pipe({
|
||||||
|
name: 'fiatShortener'
|
||||||
|
})
|
||||||
|
export class FiatShortenerPipe implements PipeTransform {
|
||||||
|
constructor(
|
||||||
|
@Inject(LOCALE_ID) public locale: string
|
||||||
|
) {}
|
||||||
|
|
||||||
|
transform(num: number, ...args: any[]): unknown {
|
||||||
|
const digits = args[0] || 1;
|
||||||
|
const unit = args[1] || undefined;
|
||||||
|
|
||||||
|
if (num < 1000) {
|
||||||
|
return num.toFixed(digits);
|
||||||
|
}
|
||||||
|
|
||||||
|
const lookup = [
|
||||||
|
{ value: 1, symbol: '' },
|
||||||
|
{ value: 1e3, symbol: 'k' },
|
||||||
|
{ value: 1e6, symbol: 'M' },
|
||||||
|
{ value: 1e9, symbol: 'G' },
|
||||||
|
{ value: 1e12, symbol: 'T' },
|
||||||
|
{ value: 1e15, symbol: 'P' },
|
||||||
|
{ value: 1e18, symbol: 'E' }
|
||||||
|
];
|
||||||
|
const rx = /\.0+$|(\.[0-9]*[1-9])0+$/;
|
||||||
|
const item = lookup.slice().reverse().find((item) => num >= item.value);
|
||||||
|
|
||||||
|
let result = item ? (num / item.value).toFixed(digits).replace(rx, '$1') : '0';
|
||||||
|
result = formatCurrency(parseInt(result, 10), this.locale, getCurrencySymbol('USD', 'narrow'), 'USD', '1.0-0');
|
||||||
|
|
||||||
|
return result + item.symbol;
|
||||||
|
}
|
||||||
|
}
|
Loading…
x
Reference in New Issue
Block a user