mirror of
https://github.com/mempool/mempool.git
synced 2024-12-29 09:44:26 +01:00
Merge pull request #1255 from nymkappa/feature/difficulty-chart
Create difficulty chart component
This commit is contained in:
commit
53ff599ccd
@ -199,6 +199,7 @@ class Blocks {
|
|||||||
|
|
||||||
const chunkSize = 10000;
|
const chunkSize = 10000;
|
||||||
let totaIndexed = 0;
|
let totaIndexed = 0;
|
||||||
|
let indexedThisRun = 0;
|
||||||
while (currentBlockHeight >= lastBlockToIndex) {
|
while (currentBlockHeight >= lastBlockToIndex) {
|
||||||
const endBlock = Math.max(0, lastBlockToIndex, currentBlockHeight - chunkSize + 1);
|
const endBlock = Math.max(0, lastBlockToIndex, currentBlockHeight - chunkSize + 1);
|
||||||
|
|
||||||
@ -207,9 +208,11 @@ class Blocks {
|
|||||||
if (missingBlockHeights.length <= 0) {
|
if (missingBlockHeights.length <= 0) {
|
||||||
logger.debug(`No missing blocks between #${currentBlockHeight} to #${endBlock}`);
|
logger.debug(`No missing blocks between #${currentBlockHeight} to #${endBlock}`);
|
||||||
currentBlockHeight -= chunkSize;
|
currentBlockHeight -= chunkSize;
|
||||||
|
totaIndexed += chunkSize;
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
totaIndexed += chunkSize - missingBlockHeights.length;
|
||||||
logger.debug(`Indexing ${missingBlockHeights.length} blocks from #${currentBlockHeight} to #${endBlock}`);
|
logger.debug(`Indexing ${missingBlockHeights.length} blocks from #${currentBlockHeight} to #${endBlock}`);
|
||||||
|
|
||||||
for (const blockHeight of missingBlockHeights) {
|
for (const blockHeight of missingBlockHeights) {
|
||||||
@ -219,8 +222,10 @@ class Blocks {
|
|||||||
try {
|
try {
|
||||||
if (totaIndexed % 100 === 0 || blockHeight === lastBlockToIndex) {
|
if (totaIndexed % 100 === 0 || blockHeight === lastBlockToIndex) {
|
||||||
const elapsedSeconds = Math.max(1, Math.round((new Date().getTime() / 1000) - startedAt));
|
const elapsedSeconds = Math.max(1, Math.round((new Date().getTime() / 1000) - startedAt));
|
||||||
const blockPerSeconds = Math.round(totaIndexed / elapsedSeconds);
|
const blockPerSeconds = Math.max(1, Math.round(indexedThisRun / elapsedSeconds));
|
||||||
logger.debug(`Indexing block #${blockHeight} | ~${blockPerSeconds} blocks/sec | total: ${totaIndexed} | elapsed: ${elapsedSeconds} seconds`);
|
const progress = Math.round(totaIndexed / indexingBlockAmount * 100);
|
||||||
|
const timeLeft = Math.round((indexingBlockAmount - totaIndexed) / blockPerSeconds);
|
||||||
|
logger.debug(`Indexing block #${blockHeight} | ~${blockPerSeconds} blocks/sec | total: ${totaIndexed}/${indexingBlockAmount} (${progress}%) | elapsed: ${elapsedSeconds} seconds | left: ~${timeLeft} seconds`);
|
||||||
}
|
}
|
||||||
const blockHash = await bitcoinApi.$getBlockHash(blockHeight);
|
const blockHash = await bitcoinApi.$getBlockHash(blockHeight);
|
||||||
const block = await bitcoinApi.$getBlock(blockHash);
|
const block = await bitcoinApi.$getBlock(blockHash);
|
||||||
@ -228,6 +233,7 @@ class Blocks {
|
|||||||
const blockExtended = await this.$getBlockExtended(block, transactions);
|
const blockExtended = await this.$getBlockExtended(block, transactions);
|
||||||
await blocksRepository.$saveBlockInDatabase(blockExtended);
|
await blocksRepository.$saveBlockInDatabase(blockExtended);
|
||||||
++totaIndexed;
|
++totaIndexed;
|
||||||
|
++indexedThisRun;
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
logger.err(`Something went wrong while indexing blocks.` + e);
|
logger.err(`Something went wrong while indexing blocks.` + e);
|
||||||
}
|
}
|
||||||
|
@ -69,6 +69,19 @@ class Mining {
|
|||||||
emptyBlocks: emptyBlocks,
|
emptyBlocks: emptyBlocks,
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Return the historical difficulty adjustments and oldest indexed block timestamp
|
||||||
|
*/
|
||||||
|
public async $getHistoricalDifficulty(interval: string | null): Promise<object> {
|
||||||
|
const difficultyAdjustments = await BlocksRepository.$getBlocksDifficulty(interval);
|
||||||
|
const oldestBlock = new Date(await BlocksRepository.$oldestBlockTimestamp());
|
||||||
|
|
||||||
|
return {
|
||||||
|
adjustments: difficultyAdjustments,
|
||||||
|
oldestIndexedBlockTimestamp: oldestBlock.getTime(),
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
export default new Mining();
|
export default new Mining();
|
||||||
|
@ -259,10 +259,7 @@ class Server {
|
|||||||
;
|
;
|
||||||
}
|
}
|
||||||
|
|
||||||
const indexingAvailable =
|
if (Common.indexingEnabled()) {
|
||||||
['mainnet', 'testnet', 'signet'].includes(config.MEMPOOL.NETWORK) &&
|
|
||||||
config.DATABASE.ENABLED === true;
|
|
||||||
if (indexingAvailable) {
|
|
||||||
this.app
|
this.app
|
||||||
.get(config.MEMPOOL.API_URL_PREFIX + 'mining/pools/24h', routes.$getPools.bind(routes, '24h'))
|
.get(config.MEMPOOL.API_URL_PREFIX + 'mining/pools/24h', routes.$getPools.bind(routes, '24h'))
|
||||||
.get(config.MEMPOOL.API_URL_PREFIX + 'mining/pools/3d', routes.$getPools.bind(routes, '3d'))
|
.get(config.MEMPOOL.API_URL_PREFIX + 'mining/pools/3d', routes.$getPools.bind(routes, '3d'))
|
||||||
@ -277,7 +274,9 @@ class Server {
|
|||||||
.get(config.MEMPOOL.API_URL_PREFIX + 'mining/pool/:poolId/blocks', routes.$getPoolBlocks)
|
.get(config.MEMPOOL.API_URL_PREFIX + 'mining/pool/:poolId/blocks', routes.$getPoolBlocks)
|
||||||
.get(config.MEMPOOL.API_URL_PREFIX + 'mining/pool/:poolId/blocks/:height', routes.$getPoolBlocks)
|
.get(config.MEMPOOL.API_URL_PREFIX + 'mining/pool/:poolId/blocks/:height', routes.$getPoolBlocks)
|
||||||
.get(config.MEMPOOL.API_URL_PREFIX + 'mining/pool/:poolId', routes.$getPool)
|
.get(config.MEMPOOL.API_URL_PREFIX + 'mining/pool/:poolId', routes.$getPool)
|
||||||
.get(config.MEMPOOL.API_URL_PREFIX + 'mining/pool/:poolId/:interval', routes.$getPool);
|
.get(config.MEMPOOL.API_URL_PREFIX + 'mining/pool/:poolId/:interval', routes.$getPool)
|
||||||
|
.get(config.MEMPOOL.API_URL_PREFIX + 'mining/difficulty', routes.$getHistoricalDifficulty)
|
||||||
|
.get(config.MEMPOOL.API_URL_PREFIX + 'mining/difficulty/:interval', routes.$getHistoricalDifficulty);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (config.BISQ.ENABLED) {
|
if (config.BISQ.ENABLED) {
|
||||||
|
@ -223,6 +223,30 @@ class BlocksRepository {
|
|||||||
|
|
||||||
return rows[0];
|
return rows[0];
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Return blocks difficulty
|
||||||
|
*/
|
||||||
|
public async $getBlocksDifficulty(interval: string | null): Promise<object[]> {
|
||||||
|
interval = Common.getSqlInterval(interval);
|
||||||
|
|
||||||
|
const connection = await DB.pool.getConnection();
|
||||||
|
|
||||||
|
let query = `SELECT MIN(UNIX_TIMESTAMP(blockTimestamp)) as timestamp, difficulty, height
|
||||||
|
FROM blocks`;
|
||||||
|
|
||||||
|
if (interval) {
|
||||||
|
query += ` WHERE blockTimestamp BETWEEN DATE_SUB(NOW(), INTERVAL ${interval}) AND NOW()`;
|
||||||
|
}
|
||||||
|
|
||||||
|
query += ` GROUP BY difficulty
|
||||||
|
ORDER BY blockTimestamp DESC`;
|
||||||
|
|
||||||
|
const [rows]: any[] = await connection.query(query);
|
||||||
|
connection.release();
|
||||||
|
|
||||||
|
return rows;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
export default new BlocksRepository();
|
export default new BlocksRepository();
|
||||||
|
@ -575,6 +575,18 @@ class Routes {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public async $getHistoricalDifficulty(req: Request, res: Response) {
|
||||||
|
try {
|
||||||
|
const stats = await mining.$getHistoricalDifficulty(req.params.interval ?? null);
|
||||||
|
res.header('Pragma', 'public');
|
||||||
|
res.header('Cache-control', 'public');
|
||||||
|
res.setHeader('Expires', new Date(Date.now() + 1000 * 300).toUTCString());
|
||||||
|
res.json(stats);
|
||||||
|
} catch (e) {
|
||||||
|
res.status(500).send(e instanceof Error ? e.message : e);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
public async getBlock(req: Request, res: Response) {
|
public async getBlock(req: Request, res: Response) {
|
||||||
try {
|
try {
|
||||||
const result = await bitcoinApi.$getBlock(req.params.hash);
|
const result = await bitcoinApi.$getBlock(req.params.hash);
|
||||||
|
@ -27,6 +27,7 @@ import { AssetGroupComponent } from './components/assets/asset-group/asset-group
|
|||||||
import { AssetsFeaturedComponent } from './components/assets/assets-featured/assets-featured.component';
|
import { AssetsFeaturedComponent } from './components/assets/assets-featured/assets-featured.component';
|
||||||
import { AssetsComponent } from './components/assets/assets.component';
|
import { AssetsComponent } from './components/assets/assets.component';
|
||||||
import { PoolComponent } from './components/pool/pool.component';
|
import { PoolComponent } from './components/pool/pool.component';
|
||||||
|
import { DifficultyChartComponent } from './components/difficulty-chart/difficulty-chart.component';
|
||||||
|
|
||||||
let routes: Routes = [
|
let routes: Routes = [
|
||||||
{
|
{
|
||||||
@ -63,6 +64,10 @@ let routes: Routes = [
|
|||||||
path: 'blocks',
|
path: 'blocks',
|
||||||
component: LatestBlocksComponent,
|
component: LatestBlocksComponent,
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
path: 'mining/difficulty',
|
||||||
|
component: DifficultyChartComponent,
|
||||||
|
},
|
||||||
{
|
{
|
||||||
path: 'mining/pools',
|
path: 'mining/pools',
|
||||||
component: PoolRankingComponent,
|
component: PoolRankingComponent,
|
||||||
@ -155,6 +160,10 @@ let routes: Routes = [
|
|||||||
path: 'blocks',
|
path: 'blocks',
|
||||||
component: LatestBlocksComponent,
|
component: LatestBlocksComponent,
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
path: 'mining/difficulty',
|
||||||
|
component: DifficultyChartComponent,
|
||||||
|
},
|
||||||
{
|
{
|
||||||
path: 'mining/pools',
|
path: 'mining/pools',
|
||||||
component: PoolRankingComponent,
|
component: PoolRankingComponent,
|
||||||
@ -241,6 +250,10 @@ let routes: Routes = [
|
|||||||
path: 'blocks',
|
path: 'blocks',
|
||||||
component: LatestBlocksComponent,
|
component: LatestBlocksComponent,
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
path: 'mining/difficulty',
|
||||||
|
component: DifficultyChartComponent,
|
||||||
|
},
|
||||||
{
|
{
|
||||||
path: 'mining/pools',
|
path: 'mining/pools',
|
||||||
component: PoolRankingComponent,
|
component: PoolRankingComponent,
|
||||||
|
@ -68,6 +68,7 @@ import { PushTransactionComponent } from './components/push-transaction/push-tra
|
|||||||
import { NgbModule } from '@ng-bootstrap/ng-bootstrap';
|
import { NgbModule } from '@ng-bootstrap/ng-bootstrap';
|
||||||
import { AssetsFeaturedComponent } from './components/assets/assets-featured/assets-featured.component';
|
import { AssetsFeaturedComponent } from './components/assets/assets-featured/assets-featured.component';
|
||||||
import { AssetGroupComponent } from './components/assets/asset-group/asset-group.component';
|
import { AssetGroupComponent } from './components/assets/asset-group/asset-group.component';
|
||||||
|
import { DifficultyChartComponent } from './components/difficulty-chart/difficulty-chart.component';
|
||||||
|
|
||||||
@NgModule({
|
@NgModule({
|
||||||
declarations: [
|
declarations: [
|
||||||
@ -118,6 +119,7 @@ import { AssetGroupComponent } from './components/assets/asset-group/asset-group
|
|||||||
AssetsNavComponent,
|
AssetsNavComponent,
|
||||||
AssetsFeaturedComponent,
|
AssetsFeaturedComponent,
|
||||||
AssetGroupComponent,
|
AssetGroupComponent,
|
||||||
|
DifficultyChartComponent,
|
||||||
],
|
],
|
||||||
imports: [
|
imports: [
|
||||||
BrowserModule.withServerTransition({ appId: 'serverApp' }),
|
BrowserModule.withServerTransition({ appId: 'serverApp' }),
|
||||||
|
@ -0,0 +1,53 @@
|
|||||||
|
<div class="container-xl">
|
||||||
|
|
||||||
|
<div *ngIf="difficultyObservable$ | async" class="" echarts [initOpts]="chartInitOptions" [options]="chartOptions"></div>
|
||||||
|
<div class="text-center loadingGraphs" *ngIf="isLoading">
|
||||||
|
<div class="spinner-border text-light"></div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="card-header mb-0 mb-lg-4">
|
||||||
|
<form [formGroup]="radioGroupForm" class="formRadioGroup" *ngIf="(difficultyObservable$ | async) as diffChanges">
|
||||||
|
<div class="btn-group btn-group-toggle" ngbRadioGroup name="radioBasic" formControlName="dateSpan">
|
||||||
|
<label ngbButtonLabel class="btn-primary btn-sm" [routerLink]="['/mining/difficulty' | relativeUrl]" *ngIf="diffChanges.availableTimespanDay >= 90">
|
||||||
|
<input ngbButton type="radio" [value]="'3m'" fragment="3m"> 3M
|
||||||
|
</label>
|
||||||
|
<label ngbButtonLabel class="btn-primary btn-sm" [routerLink]="['/mining/difficulty' | relativeUrl]" *ngIf="diffChanges.availableTimespanDay >= 180">
|
||||||
|
<input ngbButton type="radio" [value]="'6m'" fragment="6m"> 6M
|
||||||
|
</label>
|
||||||
|
<label ngbButtonLabel class="btn-primary btn-sm" [routerLink]="['/mining/difficulty' | relativeUrl]" *ngIf="diffChanges.availableTimespanDay >= 365">
|
||||||
|
<input ngbButton type="radio" [value]="'1y'" fragment="1y"> 1Y
|
||||||
|
</label>
|
||||||
|
<label ngbButtonLabel class="btn-primary btn-sm" [routerLink]="['/mining/difficulty' | relativeUrl]" *ngIf="diffChanges.availableTimespanDay >= 730">
|
||||||
|
<input ngbButton type="radio" [value]="'2y'" fragment="2y"> 2Y
|
||||||
|
</label>
|
||||||
|
<label ngbButtonLabel class="btn-primary btn-sm" [routerLink]="['/mining/difficulty' | relativeUrl]" *ngIf="diffChanges.availableTimespanDay >= 1095">
|
||||||
|
<input ngbButton type="radio" [value]="'3y'" fragment="3y"> 3Y
|
||||||
|
</label>
|
||||||
|
<label ngbButtonLabel class="btn-primary btn-sm">
|
||||||
|
<input ngbButton type="radio" [value]="'all'" [routerLink]="['/mining/difficulty' | relativeUrl]" fragment="all"> ALL
|
||||||
|
</label>
|
||||||
|
</div>
|
||||||
|
</form>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<table class="table table-borderless table-sm text-center">
|
||||||
|
<thead>
|
||||||
|
<tr>
|
||||||
|
<th i18n="mining.rank">Block</th>
|
||||||
|
<th i18n="block.timestamp">Timestamp</th>
|
||||||
|
<th i18n="mining.difficulty">Difficulty</th>
|
||||||
|
<th i18n="mining.change">Change</th>
|
||||||
|
</tr>
|
||||||
|
</thead>
|
||||||
|
<tbody *ngIf="(difficultyObservable$ | async) as diffChanges">
|
||||||
|
<tr *ngFor="let diffChange of diffChanges.data">
|
||||||
|
<td><a [routerLink]="['/block' | relativeUrl, diffChange.height]">{{ diffChange.height }}</a></td>
|
||||||
|
<td>‎{{ diffChange.timestamp * 1000 | date:'yyyy-MM-dd HH:mm' }}</td>
|
||||||
|
<td class="d-none d-md-block">{{ formatNumber(diffChange.difficulty, locale, '1.2-2') }}</td>
|
||||||
|
<td class="d-block d-md-none">{{ diffChange.difficultyShorten }}</td>
|
||||||
|
<td [style]="diffChange.change >= 0 ? 'color: #42B747' : 'color: #B74242'">{{ formatNumber(diffChange.change, locale, '1.2-2') }}%</td>
|
||||||
|
</tr>
|
||||||
|
</tbody>
|
||||||
|
</table>
|
||||||
|
|
||||||
|
</div>
|
@ -0,0 +1,151 @@
|
|||||||
|
import { Component, Inject, LOCALE_ID, OnInit } from '@angular/core';
|
||||||
|
import { EChartsOption } from 'echarts';
|
||||||
|
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 { FormBuilder, FormGroup } from '@angular/forms';
|
||||||
|
|
||||||
|
@Component({
|
||||||
|
selector: 'app-difficulty-chart',
|
||||||
|
templateUrl: './difficulty-chart.component.html',
|
||||||
|
styleUrls: ['./difficulty-chart.component.scss'],
|
||||||
|
styles: [`
|
||||||
|
.loadingGraphs {
|
||||||
|
position: absolute;
|
||||||
|
top: 38%;
|
||||||
|
left: calc(50% - 15px);
|
||||||
|
z-index: 100;
|
||||||
|
}
|
||||||
|
`],
|
||||||
|
})
|
||||||
|
export class DifficultyChartComponent implements OnInit {
|
||||||
|
radioGroupForm: FormGroup;
|
||||||
|
|
||||||
|
chartOptions: EChartsOption = {};
|
||||||
|
chartInitOptions = {
|
||||||
|
renderer: 'svg'
|
||||||
|
};
|
||||||
|
|
||||||
|
difficultyObservable$: Observable<any>;
|
||||||
|
isLoading = true;
|
||||||
|
formatNumber = formatNumber;
|
||||||
|
|
||||||
|
constructor(
|
||||||
|
@Inject(LOCALE_ID) public locale: string,
|
||||||
|
private seoService: SeoService,
|
||||||
|
private apiService: ApiService,
|
||||||
|
private formBuilder: FormBuilder,
|
||||||
|
) {
|
||||||
|
this.seoService.setTitle($localize`:@@mining.difficulty:Difficulty`);
|
||||||
|
this.radioGroupForm = this.formBuilder.group({ dateSpan: '1y' });
|
||||||
|
this.radioGroupForm.controls.dateSpan.setValue('1y');
|
||||||
|
}
|
||||||
|
|
||||||
|
ngOnInit(): void {
|
||||||
|
const powerOfTen = {
|
||||||
|
terra: Math.pow(10, 12),
|
||||||
|
giga: Math.pow(10, 9),
|
||||||
|
mega: Math.pow(10, 6),
|
||||||
|
kilo: Math.pow(10, 3),
|
||||||
|
}
|
||||||
|
|
||||||
|
this.difficultyObservable$ = this.radioGroupForm.get('dateSpan').valueChanges
|
||||||
|
.pipe(
|
||||||
|
startWith('1y'),
|
||||||
|
switchMap((timespan) => {
|
||||||
|
return this.apiService.getHistoricalDifficulty$(timespan)
|
||||||
|
.pipe(
|
||||||
|
tap(data => {
|
||||||
|
this.prepareChartOptions(data.adjustments.map(val => [val.timestamp * 1000, val.difficulty]));
|
||||||
|
this.isLoading = false;
|
||||||
|
}),
|
||||||
|
map(data => {
|
||||||
|
const availableTimespanDay = (
|
||||||
|
(new Date().getTime() / 1000) - (data.oldestIndexedBlockTimestamp / 1000)
|
||||||
|
) / 3600 / 24;
|
||||||
|
|
||||||
|
const tableData = [];
|
||||||
|
for (let i = 0; i < data.adjustments.length - 1; ++i) {
|
||||||
|
const change = (data.adjustments[i].difficulty / data.adjustments[i + 1].difficulty - 1) * 100;
|
||||||
|
let selectedPowerOfTen = { divider: powerOfTen.terra, unit: 'T' };
|
||||||
|
if (data.adjustments[i].difficulty < powerOfTen.mega) {
|
||||||
|
selectedPowerOfTen = { divider: 1, unit: '' }; // no scaling
|
||||||
|
} else if (data.adjustments[i].difficulty < powerOfTen.giga) {
|
||||||
|
selectedPowerOfTen = { divider: powerOfTen.mega, unit: 'M' };
|
||||||
|
} else if (data.adjustments[i].difficulty < powerOfTen.terra) {
|
||||||
|
selectedPowerOfTen = { divider: powerOfTen.giga, unit: 'G' };
|
||||||
|
}
|
||||||
|
|
||||||
|
tableData.push(Object.assign(data.adjustments[i], {
|
||||||
|
change: change,
|
||||||
|
difficultyShorten: formatNumber(
|
||||||
|
data.adjustments[i].difficulty / selectedPowerOfTen.divider,
|
||||||
|
this.locale, '1.2-2') + selectedPowerOfTen.unit
|
||||||
|
}));
|
||||||
|
}
|
||||||
|
return {
|
||||||
|
availableTimespanDay: availableTimespanDay,
|
||||||
|
data: tableData
|
||||||
|
};
|
||||||
|
}),
|
||||||
|
);
|
||||||
|
}),
|
||||||
|
share()
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
prepareChartOptions(data) {
|
||||||
|
this.chartOptions = {
|
||||||
|
title: {
|
||||||
|
text: $localize`:@@mining.difficulty:Difficulty`,
|
||||||
|
left: 'center',
|
||||||
|
textStyle: {
|
||||||
|
color: '#FFF',
|
||||||
|
},
|
||||||
|
},
|
||||||
|
tooltip: {
|
||||||
|
show: true,
|
||||||
|
trigger: 'axis',
|
||||||
|
},
|
||||||
|
axisPointer: {
|
||||||
|
type: 'line',
|
||||||
|
},
|
||||||
|
xAxis: [
|
||||||
|
{
|
||||||
|
type: 'time',
|
||||||
|
}
|
||||||
|
],
|
||||||
|
yAxis: {
|
||||||
|
type: 'value',
|
||||||
|
axisLabel: {
|
||||||
|
fontSize: 11,
|
||||||
|
formatter: (val) => {
|
||||||
|
const diff = val / Math.pow(10, 12); // terra
|
||||||
|
return diff.toString() + 'T';
|
||||||
|
}
|
||||||
|
},
|
||||||
|
splitLine: {
|
||||||
|
lineStyle: {
|
||||||
|
type: 'dotted',
|
||||||
|
color: '#ffffff66',
|
||||||
|
opacity: 0.25,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
series: [
|
||||||
|
{
|
||||||
|
data: data,
|
||||||
|
type: 'line',
|
||||||
|
smooth: false,
|
||||||
|
lineStyle: {
|
||||||
|
width: 3,
|
||||||
|
},
|
||||||
|
areaStyle: {}
|
||||||
|
},
|
||||||
|
],
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
@ -43,7 +43,7 @@
|
|||||||
</form>
|
</form>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<table class="table table-borderless text-center pools-table" [alwaysCallback]="true" infiniteScroll [infiniteScrollDistance]="1.5" [infiniteScrollUpDistance]="1.5" [infiniteScrollThrottle]="50">
|
<table class="table table-borderless text-center pools-table">
|
||||||
<thead>
|
<thead>
|
||||||
<tr>
|
<tr>
|
||||||
<th class="d-none d-md-block" i18n="mining.rank">Rank</th>
|
<th class="d-none d-md-block" i18n="mining.rank">Rank</th>
|
||||||
|
@ -1,4 +1,4 @@
|
|||||||
import { Component, OnDestroy, OnInit } from '@angular/core';
|
import { Component, OnInit } from '@angular/core';
|
||||||
import { FormBuilder, FormGroup } from '@angular/forms';
|
import { FormBuilder, FormGroup } from '@angular/forms';
|
||||||
import { Router } from '@angular/router';
|
import { Router } from '@angular/router';
|
||||||
import { EChartsOption, PieSeriesOption } from 'echarts';
|
import { EChartsOption, PieSeriesOption } from 'echarts';
|
||||||
@ -23,7 +23,7 @@ import { StateService } from '../../services/state.service';
|
|||||||
}
|
}
|
||||||
`],
|
`],
|
||||||
})
|
})
|
||||||
export class PoolRankingComponent implements OnInit, OnDestroy {
|
export class PoolRankingComponent implements OnInit {
|
||||||
poolsWindowPreference: string;
|
poolsWindowPreference: string;
|
||||||
radioGroupForm: FormGroup;
|
radioGroupForm: FormGroup;
|
||||||
|
|
||||||
@ -90,9 +90,6 @@ export class PoolRankingComponent implements OnInit, OnDestroy {
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
ngOnDestroy(): void {
|
|
||||||
}
|
|
||||||
|
|
||||||
formatPoolUI(pool: SinglePoolStats) {
|
formatPoolUI(pool: SinglePoolStats) {
|
||||||
pool['blockText'] = pool.blockCount.toString() + ` (${pool.share}%)`;
|
pool['blockText'] = pool.blockCount.toString() + ` (${pool.share}%)`;
|
||||||
return pool;
|
return pool;
|
||||||
@ -110,7 +107,7 @@ export class PoolRankingComponent implements OnInit, OnDestroy {
|
|||||||
if (parseFloat(pool.share) < poolShareThreshold) {
|
if (parseFloat(pool.share) < poolShareThreshold) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
data.push(<PieSeriesOption>{
|
data.push({
|
||||||
value: pool.share,
|
value: pool.share,
|
||||||
name: pool.name + (this.isMobile() ? `` : ` (${pool.share}%)`),
|
name: pool.name + (this.isMobile() ? `` : ` (${pool.share}%)`),
|
||||||
label: {
|
label: {
|
||||||
@ -118,9 +115,9 @@ export class PoolRankingComponent implements OnInit, OnDestroy {
|
|||||||
overflow: 'break',
|
overflow: 'break',
|
||||||
},
|
},
|
||||||
tooltip: {
|
tooltip: {
|
||||||
backgroundColor: "#282d47",
|
backgroundColor: '#282d47',
|
||||||
textStyle: {
|
textStyle: {
|
||||||
color: "#FFFFFF",
|
color: '#FFFFFF',
|
||||||
},
|
},
|
||||||
formatter: () => {
|
formatter: () => {
|
||||||
if (this.poolsWindowPreference === '24h') {
|
if (this.poolsWindowPreference === '24h') {
|
||||||
@ -134,7 +131,7 @@ export class PoolRankingComponent implements OnInit, OnDestroy {
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
data: pool.poolId,
|
data: pool.poolId,
|
||||||
});
|
} as PieSeriesOption);
|
||||||
});
|
});
|
||||||
return data;
|
return data;
|
||||||
}
|
}
|
||||||
@ -208,10 +205,10 @@ export class PoolRankingComponent implements OnInit, OnDestroy {
|
|||||||
|
|
||||||
this.chartInstance = ec;
|
this.chartInstance = ec;
|
||||||
this.chartInstance.on('click', (e) => {
|
this.chartInstance.on('click', (e) => {
|
||||||
this.router.navigate(['/mining/pool/', e.data.data]);
|
this.router.navigate(['/mining/pool/', e.data.data]);
|
||||||
})
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Default mining stats if something goes wrong
|
* Default mining stats if something goes wrong
|
||||||
*/
|
*/
|
||||||
|
@ -129,12 +129,18 @@ export class ApiService {
|
|||||||
return this.httpClient.post<any>(this.apiBaseUrl + this.apiBasePath + '/api/tx', hexPayload, { responseType: 'text' as 'json'});
|
return this.httpClient.post<any>(this.apiBaseUrl + this.apiBasePath + '/api/tx', hexPayload, { responseType: 'text' as 'json'});
|
||||||
}
|
}
|
||||||
|
|
||||||
listPools$(interval: string | null) : Observable<PoolsStats> {
|
listPools$(interval: string | undefined) : Observable<PoolsStats> {
|
||||||
return this.httpClient.get<PoolsStats>(this.apiBaseUrl + this.apiBasePath + `/api/v1/mining/pools/${interval}`);
|
return this.httpClient.get<PoolsStats>(
|
||||||
|
this.apiBaseUrl + this.apiBasePath + `/api/v1/mining/pools` +
|
||||||
|
(interval !== undefined ? `/${interval}` : '')
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
getPoolStats$(poolId: number, interval: string | null): Observable<PoolStat> {
|
getPoolStats$(poolId: number, interval: string | undefined): Observable<PoolStat> {
|
||||||
return this.httpClient.get<PoolStat>(this.apiBaseUrl + this.apiBasePath + `/api/v1/mining/pool/${poolId}/${interval}`);
|
return this.httpClient.get<PoolStat>(
|
||||||
|
this.apiBaseUrl + this.apiBasePath + `/api/v1/mining/pool/${poolId}` +
|
||||||
|
(interval !== undefined ? `/${interval}` : '')
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
getPoolBlocks$(poolId: number, fromHeight: number): Observable<BlockExtended[]> {
|
getPoolBlocks$(poolId: number, fromHeight: number): Observable<BlockExtended[]> {
|
||||||
@ -143,4 +149,11 @@ export class ApiService {
|
|||||||
(fromHeight !== undefined ? `/${fromHeight}` : '')
|
(fromHeight !== undefined ? `/${fromHeight}` : '')
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
getHistoricalDifficulty$(interval: string | undefined): Observable<any> {
|
||||||
|
return this.httpClient.get<any[]>(
|
||||||
|
this.apiBaseUrl + this.apiBasePath + `/api/v1/mining/difficulty` +
|
||||||
|
(interval !== undefined ? `/${interval}` : '')
|
||||||
|
);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
Loading…
Reference in New Issue
Block a user