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 HashratesRepository from '../repositories/HashratesRepository';
 | 
			
		||||
import indexer from '../indexer';
 | 
			
		||||
import fiatConversion from './fiat-conversion';
 | 
			
		||||
import RatesRepository from '../repositories/RatesRepository';
 | 
			
		||||
import database from '../database';
 | 
			
		||||
import poolsParser from './pools-parser';
 | 
			
		||||
import BlocksSummariesRepository from '../repositories/BlocksSummariesRepository';
 | 
			
		||||
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.coinbaseTx = transactionUtils.stripCoinbaseTransaction(transactions[0]);
 | 
			
		||||
    blockExtended.extras.coinbaseRaw = blockExtended.extras.coinbaseTx.vin[0].scriptsig;
 | 
			
		||||
    blockExtended.extras.usd = fiatConversion.getConversionRates().USD;
 | 
			
		||||
 | 
			
		||||
    if (block.height === 0) {
 | 
			
		||||
      blockExtended.extras.medianFee = 0; // 50th percentiles
 | 
			
		||||
 | 
			
		||||
@ -31,7 +31,7 @@ class Mining {
 | 
			
		||||
   */
 | 
			
		||||
  public async $getHistoricalBlockFees(interval: string | null = null): Promise<any> {
 | 
			
		||||
    return await BlocksRepository.$getHistoricalBlockFees(
 | 
			
		||||
      this.getTimeRange(interval),
 | 
			
		||||
      this.getTimeRangeForAmounts(interval),
 | 
			
		||||
      Common.getSqlInterval(interval)
 | 
			
		||||
    );
 | 
			
		||||
  }
 | 
			
		||||
@ -41,7 +41,7 @@ class Mining {
 | 
			
		||||
   */
 | 
			
		||||
  public async $getHistoricalBlockRewards(interval: string | null = null): Promise<any> {
 | 
			
		||||
    return await BlocksRepository.$getHistoricalBlockRewards(
 | 
			
		||||
      this.getTimeRange(interval),
 | 
			
		||||
      this.getTimeRangeForAmounts(interval),
 | 
			
		||||
      Common.getSqlInterval(interval)
 | 
			
		||||
    );
 | 
			
		||||
  }
 | 
			
		||||
@ -462,6 +462,21 @@ class Mining {
 | 
			
		||||
    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 {
 | 
			
		||||
    switch (interval) {
 | 
			
		||||
      case '3y': return 43200; // 12h
 | 
			
		||||
@ -473,7 +488,7 @@ class Mining {
 | 
			
		||||
      case '1w': return 300; // 5min
 | 
			
		||||
      case '3d': return 1;
 | 
			
		||||
      case '24h': return 1;
 | 
			
		||||
      default: return 86400; // 24h
 | 
			
		||||
      default: return 86400;
 | 
			
		||||
    }
 | 
			
		||||
  }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
@ -109,6 +109,7 @@ export interface BlockExtension {
 | 
			
		||||
  avgFee?: number;
 | 
			
		||||
  avgFeeRate?: number;
 | 
			
		||||
  coinbaseRaw?: string;
 | 
			
		||||
  usd?: number | null;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
export interface BlockExtended extends IEsploraApi.Block {
 | 
			
		||||
 | 
			
		||||
@ -256,7 +256,7 @@ class BlocksRepository {
 | 
			
		||||
 | 
			
		||||
    const params: any[] = [];
 | 
			
		||||
    let query = ` SELECT
 | 
			
		||||
      height,
 | 
			
		||||
      blocks.height,
 | 
			
		||||
      hash as id,
 | 
			
		||||
      UNIX_TIMESTAMP(blocks.blockTimestamp) as blockTimestamp,
 | 
			
		||||
      size,
 | 
			
		||||
@ -274,8 +274,10 @@ class BlocksRepository {
 | 
			
		||||
      merkle_root,
 | 
			
		||||
      previous_block_hash as previousblockhash,
 | 
			
		||||
      avg_fee,
 | 
			
		||||
      avg_fee_rate
 | 
			
		||||
      avg_fee_rate,
 | 
			
		||||
      IFNULL(JSON_EXTRACT(rates.bisq_rates, '$.USD'), null) as usd
 | 
			
		||||
      FROM blocks
 | 
			
		||||
      LEFT JOIN rates on rates.height = blocks.height
 | 
			
		||||
      WHERE pool_id = ?`;
 | 
			
		||||
    params.push(pool.id);
 | 
			
		||||
 | 
			
		||||
@ -308,7 +310,7 @@ class BlocksRepository {
 | 
			
		||||
  public async $getBlockByHeight(height: number): Promise<object | null> {
 | 
			
		||||
    try {
 | 
			
		||||
      const [rows]: any[] = await DB.query(`SELECT
 | 
			
		||||
        height,
 | 
			
		||||
        blocks.height,
 | 
			
		||||
        hash,
 | 
			
		||||
        hash as id,
 | 
			
		||||
        UNIX_TIMESTAMP(blocks.blockTimestamp) as blockTimestamp,
 | 
			
		||||
@ -333,10 +335,12 @@ class BlocksRepository {
 | 
			
		||||
        merkle_root,
 | 
			
		||||
        previous_block_hash as previousblockhash,
 | 
			
		||||
        avg_fee,
 | 
			
		||||
        avg_fee_rate
 | 
			
		||||
        avg_fee_rate,
 | 
			
		||||
        IFNULL(JSON_EXTRACT(rates.bisq_rates, '$.USD'), null) as usd
 | 
			
		||||
        FROM blocks
 | 
			
		||||
        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) {
 | 
			
		||||
@ -357,12 +361,14 @@ class BlocksRepository {
 | 
			
		||||
  public async $getBlockByHash(hash: string): Promise<object | null> {
 | 
			
		||||
    try {
 | 
			
		||||
      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.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
 | 
			
		||||
        JOIN pools ON blocks.pool_id = pools.id
 | 
			
		||||
        LEFT JOIN rates on rates.height = blocks.height
 | 
			
		||||
        WHERE hash = '${hash}';
 | 
			
		||||
      `;
 | 
			
		||||
      const [rows]: any[] = await DB.query(query);
 | 
			
		||||
@ -473,10 +479,12 @@ class BlocksRepository {
 | 
			
		||||
  public async $getHistoricalBlockFees(div: number, interval: string | null): Promise<any> {
 | 
			
		||||
    try {
 | 
			
		||||
      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(fees) as INT) as avgFees
 | 
			
		||||
        FROM blocks`;
 | 
			
		||||
        CAST(AVG(fees) as INT) as avgFees,
 | 
			
		||||
        IFNULL(JSON_EXTRACT(rates.bisq_rates, '$.USD'), null) as usd
 | 
			
		||||
        FROM blocks
 | 
			
		||||
        LEFT JOIN rates on rates.height = blocks.height`;
 | 
			
		||||
 | 
			
		||||
      if (interval !== null) {
 | 
			
		||||
        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> {
 | 
			
		||||
    try {
 | 
			
		||||
      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(reward) as INT) as avgRewards
 | 
			
		||||
        FROM blocks`;
 | 
			
		||||
        CAST(AVG(reward) as INT) as avgRewards,
 | 
			
		||||
        IFNULL(JSON_EXTRACT(rates.bisq_rates, '$.USD'), null) as usd
 | 
			
		||||
        FROM blocks
 | 
			
		||||
        LEFT JOIN rates on rates.height = blocks.height`;
 | 
			
		||||
 | 
			
		||||
      if (interval !== null) {
 | 
			
		||||
        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,
 | 
			
		||||
        slug: block.pool_slug,
 | 
			
		||||
      } : 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 { HttpCacheInterceptor } from './services/http-cache.interceptor';
 | 
			
		||||
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 { 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,
 | 
			
		||||
    LanguageService,
 | 
			
		||||
    ShortenStringPipe,
 | 
			
		||||
    FiatShortenerPipe,
 | 
			
		||||
    CapAddressPipe,
 | 
			
		||||
    { 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>`;
 | 
			
		||||
 | 
			
		||||
          for (const pool of data.reverse()) {
 | 
			
		||||
            tooltip += `${pool.marker} ${pool.seriesName}: ${pool.data[1]} sats/vByte<br>`;
 | 
			
		||||
          for (const rate of data.reverse()) {
 | 
			
		||||
            tooltip += `${rate.marker} ${rate.seriesName}: ${rate.data[1]} sats/vByte<br>`;
 | 
			
		||||
          }
 | 
			
		||||
 | 
			
		||||
          if (['24h', '3d'].includes(this.timespan)) {
 | 
			
		||||
 | 
			
		||||
@ -4,12 +4,13 @@ import { Observable } from 'rxjs';
 | 
			
		||||
import { map, share, startWith, switchMap, tap } from 'rxjs/operators';
 | 
			
		||||
import { ApiService } from 'src/app/services/api.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 { 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 { MiningService } from 'src/app/services/mining.service';
 | 
			
		||||
import { ActivatedRoute } from '@angular/router';
 | 
			
		||||
import { FiatShortenerPipe } from 'src/app/shared/pipes/fiat-shortener.pipe';
 | 
			
		||||
 | 
			
		||||
@Component({
 | 
			
		||||
  selector: 'app-block-fees-graph',
 | 
			
		||||
@ -51,6 +52,7 @@ export class BlockFeesGraphComponent implements OnInit {
 | 
			
		||||
    private storageService: StorageService,
 | 
			
		||||
    private miningService: MiningService,
 | 
			
		||||
    private route: ActivatedRoute,
 | 
			
		||||
    private fiatShortenerPipe: FiatShortenerPipe,
 | 
			
		||||
  ) {
 | 
			
		||||
    this.radioGroupForm = this.formBuilder.group({ dateSpan: '1y' });
 | 
			
		||||
    this.radioGroupForm.controls.dateSpan.setValue('1y');
 | 
			
		||||
@ -82,6 +84,7 @@ export class BlockFeesGraphComponent implements OnInit {
 | 
			
		||||
              tap((response) => {
 | 
			
		||||
                this.prepareChartOptions({
 | 
			
		||||
                  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;
 | 
			
		||||
              }),
 | 
			
		||||
@ -98,16 +101,17 @@ export class BlockFeesGraphComponent implements OnInit {
 | 
			
		||||
 | 
			
		||||
  prepareChartOptions(data) {
 | 
			
		||||
    this.chartOptions = {
 | 
			
		||||
      animation: false,
 | 
			
		||||
      color: [
 | 
			
		||||
        new graphic.LinearGradient(0, 0, 0, 0.65, [
 | 
			
		||||
          { offset: 0, color: '#F4511E' },
 | 
			
		||||
          { offset: 0.25, color: '#FB8C00' },
 | 
			
		||||
          { offset: 0.5, color: '#FFB300' },
 | 
			
		||||
          { offset: 0.75, color: '#FDD835' },
 | 
			
		||||
          { offset: 1, color: '#7CB342' }
 | 
			
		||||
        new graphic.LinearGradient(0, 0, 0, 1, [
 | 
			
		||||
          { offset: 0, color: '#00ACC1' },
 | 
			
		||||
          { offset: 1, color: '#0D47A1' },
 | 
			
		||||
        ]),
 | 
			
		||||
        new graphic.LinearGradient(0, 0, 0, 1, [
 | 
			
		||||
          { offset: 0, color: '#FDD835' },
 | 
			
		||||
          { offset: 1, color: '#FB8C00' },
 | 
			
		||||
        ]),
 | 
			
		||||
      ],
 | 
			
		||||
      animation: false,
 | 
			
		||||
      grid: {
 | 
			
		||||
        top: 30,
 | 
			
		||||
        bottom: 80,
 | 
			
		||||
@ -128,28 +132,52 @@ export class BlockFeesGraphComponent implements OnInit {
 | 
			
		||||
          align: 'left',
 | 
			
		||||
        },
 | 
			
		||||
        borderColor: '#000',
 | 
			
		||||
        formatter: (ticks) => {
 | 
			
		||||
          let tooltip = `<b style="color: white; margin-left: 2px">${formatterXAxis(this.locale, this.timespan, parseInt(ticks[0].axisValue, 10))}</b><br>`;
 | 
			
		||||
          tooltip += `${ticks[0].marker} ${ticks[0].seriesName}: ${formatNumber(ticks[0].data[1], this.locale, '1.3-3')} BTC`;
 | 
			
		||||
          tooltip += `<br>`;
 | 
			
		||||
        formatter: function (data) {
 | 
			
		||||
          if (data.length <= 0) {
 | 
			
		||||
            return '';
 | 
			
		||||
          }
 | 
			
		||||
          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)) {
 | 
			
		||||
            tooltip += `<small>` + $localize`At block: ${ticks[0].data[2]}` + `</small>`;
 | 
			
		||||
          } else {
 | 
			
		||||
            tooltip += `<small>` + $localize`Around block: ${ticks[0].data[2]}` + `</small>`;
 | 
			
		||||
          for (const tick of data) {
 | 
			
		||||
            if (tick.seriesIndex === 0) {
 | 
			
		||||
              tooltip += `${tick.marker} ${tick.seriesName}: ${formatNumber(tick.data[1], this.locale, '1.3-3')} BTC<br>`;
 | 
			
		||||
            } 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;
 | 
			
		||||
        }
 | 
			
		||||
        }.bind(this)
 | 
			
		||||
      },
 | 
			
		||||
      xAxis: {
 | 
			
		||||
        name: formatterXAxisLabel(this.locale, this.timespan),
 | 
			
		||||
        nameLocation: 'middle',
 | 
			
		||||
        nameTextStyle: {
 | 
			
		||||
          padding: [10, 0, 0, 0],
 | 
			
		||||
        },
 | 
			
		||||
      xAxis: data.blockFees.length === 0 ? undefined :
 | 
			
		||||
      {
 | 
			
		||||
        type: 'time',
 | 
			
		||||
        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: [
 | 
			
		||||
        {
 | 
			
		||||
@ -160,6 +188,9 @@ export class BlockFeesGraphComponent implements OnInit {
 | 
			
		||||
              return `${val} BTC`;
 | 
			
		||||
            }
 | 
			
		||||
          },
 | 
			
		||||
          max: (value) => {
 | 
			
		||||
            return Math.floor(value.max * 2 * 10) / 10;
 | 
			
		||||
          },
 | 
			
		||||
          splitLine: {
 | 
			
		||||
            lineStyle: {
 | 
			
		||||
              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: [
 | 
			
		||||
        {
 | 
			
		||||
          legendHoverLink: false,
 | 
			
		||||
          zlevel: 0,
 | 
			
		||||
          name: $localize`:@@c20172223f84462032664d717d739297e5a9e2fe:Fees`,
 | 
			
		||||
          showSymbol: false,
 | 
			
		||||
          symbol: 'none',
 | 
			
		||||
          yAxisIndex: 0,
 | 
			
		||||
          name: 'Fees BTC',
 | 
			
		||||
          data: data.blockFees,
 | 
			
		||||
          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: {
 | 
			
		||||
            width: 2,
 | 
			
		||||
          },
 | 
			
		||||
            opacity: 0.75,
 | 
			
		||||
          }
 | 
			
		||||
        },
 | 
			
		||||
      ],
 | 
			
		||||
      dataZoom: [{
 | 
			
		||||
 | 
			
		||||
@ -4,12 +4,13 @@ import { Observable } from 'rxjs';
 | 
			
		||||
import { map, share, startWith, switchMap, tap } from 'rxjs/operators';
 | 
			
		||||
import { ApiService } from 'src/app/services/api.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 { 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 { StorageService } from 'src/app/services/storage.service';
 | 
			
		||||
import { ActivatedRoute } from '@angular/router';
 | 
			
		||||
import { FiatShortenerPipe } from 'src/app/shared/pipes/fiat-shortener.pipe';
 | 
			
		||||
 | 
			
		||||
@Component({
 | 
			
		||||
  selector: 'app-block-rewards-graph',
 | 
			
		||||
@ -51,6 +52,7 @@ export class BlockRewardsGraphComponent implements OnInit {
 | 
			
		||||
    private miningService: MiningService,
 | 
			
		||||
    private storageService: StorageService,
 | 
			
		||||
    private route: ActivatedRoute,
 | 
			
		||||
    private fiatShortenerPipe: FiatShortenerPipe,
 | 
			
		||||
  ) {
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
@ -80,6 +82,7 @@ export class BlockRewardsGraphComponent implements OnInit {
 | 
			
		||||
              tap((response) => {
 | 
			
		||||
                this.prepareChartOptions({
 | 
			
		||||
                  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;
 | 
			
		||||
              }),
 | 
			
		||||
@ -95,15 +98,18 @@ export class BlockRewardsGraphComponent implements OnInit {
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  prepareChartOptions(data) {
 | 
			
		||||
    const scaleFactor = 0.1;
 | 
			
		||||
 | 
			
		||||
    this.chartOptions = {
 | 
			
		||||
      animation: false,
 | 
			
		||||
      color: [
 | 
			
		||||
        new graphic.LinearGradient(0, 0, 0, 0.65, [
 | 
			
		||||
          { offset: 0, color: '#F4511E' },
 | 
			
		||||
          { offset: 0.25, color: '#FB8C00' },
 | 
			
		||||
          { offset: 0.5, color: '#FFB300' },
 | 
			
		||||
          { offset: 0.75, color: '#FDD835' },
 | 
			
		||||
          { offset: 1, color: '#7CB342' }
 | 
			
		||||
        new graphic.LinearGradient(0, 0, 0, 1, [
 | 
			
		||||
          { offset: 0, color: '#00ACC1' },
 | 
			
		||||
          { offset: 1, color: '#0D47A1' },
 | 
			
		||||
        ]),
 | 
			
		||||
        new graphic.LinearGradient(0, 0, 0, 1, [
 | 
			
		||||
          { offset: 0, color: '#FDD835' },
 | 
			
		||||
          { offset: 1, color: '#FB8C00' },
 | 
			
		||||
        ]),
 | 
			
		||||
      ],
 | 
			
		||||
      grid: {
 | 
			
		||||
@ -126,33 +132,55 @@ export class BlockRewardsGraphComponent implements OnInit {
 | 
			
		||||
          align: 'left',
 | 
			
		||||
        },
 | 
			
		||||
        borderColor: '#000',
 | 
			
		||||
        formatter: (ticks) => {
 | 
			
		||||
          let tooltip = `<b style="color: white; margin-left: 2px">${formatterXAxis(this.locale, this.timespan, parseInt(ticks[0].axisValue, 10))}</b><br>`;
 | 
			
		||||
          tooltip += `${ticks[0].marker} ${ticks[0].seriesName}: ${formatNumber(ticks[0].data[1], this.locale, '1.3-3')} BTC`;
 | 
			
		||||
          tooltip += `<br>`;
 | 
			
		||||
        formatter: function (data) {
 | 
			
		||||
          if (data.length <= 0) {
 | 
			
		||||
            return '';
 | 
			
		||||
          }
 | 
			
		||||
          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)) {
 | 
			
		||||
            tooltip += `<small>` + $localize`At block: ${ticks[0].data[2]}` + `</small>`;
 | 
			
		||||
          } else {
 | 
			
		||||
            tooltip += `<small>` + $localize`Around block: ${ticks[0].data[2]}` + `</small>`;
 | 
			
		||||
          for (const tick of data) {
 | 
			
		||||
            if (tick.seriesIndex === 0) {
 | 
			
		||||
              tooltip += `${tick.marker} ${tick.seriesName}: ${formatNumber(tick.data[1], this.locale, '1.3-3')} BTC<br>`;
 | 
			
		||||
            } 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;
 | 
			
		||||
        }
 | 
			
		||||
        }.bind(this)
 | 
			
		||||
      },
 | 
			
		||||
      xAxis: {
 | 
			
		||||
        name: formatterXAxisLabel(this.locale, this.timespan),
 | 
			
		||||
        nameLocation: 'middle',
 | 
			
		||||
        nameTextStyle: {
 | 
			
		||||
          padding: [10, 0, 0, 0],
 | 
			
		||||
        },
 | 
			
		||||
      xAxis: data.blockRewards.length === 0 ? undefined :
 | 
			
		||||
      {
 | 
			
		||||
        type: 'time',
 | 
			
		||||
        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: [
 | 
			
		||||
        {
 | 
			
		||||
          min: value => Math.round(10 * value.min * 0.99) / 10,
 | 
			
		||||
          max: value => Math.round(10 * value.max * 1.01) / 10,
 | 
			
		||||
          type: 'value',
 | 
			
		||||
          axisLabel: {
 | 
			
		||||
            color: 'rgb(110, 112, 121)',
 | 
			
		||||
@ -160,6 +188,12 @@ export class BlockRewardsGraphComponent implements OnInit {
 | 
			
		||||
              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: {
 | 
			
		||||
            lineStyle: {
 | 
			
		||||
              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: [
 | 
			
		||||
        {
 | 
			
		||||
          legendHoverLink: false,
 | 
			
		||||
          zlevel: 0,
 | 
			
		||||
          name: $localize`:@@12f86e6747a5ad39e62d3480ddc472b1aeab5b76:Reward`,
 | 
			
		||||
          showSymbol: false,
 | 
			
		||||
          symbol: 'none',
 | 
			
		||||
          yAxisIndex: 0,
 | 
			
		||||
          name: 'Rewards BTC',
 | 
			
		||||
          data: data.blockRewards,
 | 
			
		||||
          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: {
 | 
			
		||||
            width: 2,
 | 
			
		||||
          },
 | 
			
		||||
            opacity: 0.75,
 | 
			
		||||
          }
 | 
			
		||||
        },
 | 
			
		||||
      ],
 | 
			
		||||
      dataZoom: [{
 | 
			
		||||
 | 
			
		||||
@ -351,6 +351,7 @@ export class HashrateChartComponent implements OnInit {
 | 
			
		||||
      series: data.hashrates.length === 0 ? [] : [
 | 
			
		||||
        {
 | 
			
		||||
          zlevel: 0,
 | 
			
		||||
          yAxisIndex: 0,
 | 
			
		||||
          name: $localize`:@@79a9dc5b1caca3cbeb1733a19515edacc5fc7920:Hashrate`,
 | 
			
		||||
          showSymbol: false,
 | 
			
		||||
          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