Add share % in pie chart label

This commit is contained in:
nymkappa 2022-01-21 11:17:36 +09:00
parent 5b32ab6dde
commit 8eaa9b3c7b
No known key found for this signature in database
GPG Key ID: E155910B16E8BD04
4 changed files with 93 additions and 82 deletions

View File

@ -9,8 +9,7 @@
<div class="card-header"> <div class="card-header">
<form [formGroup]="radioGroupForm" class="formRadioGroup"> <form [formGroup]="radioGroupForm" class="formRadioGroup">
<div class="btn-group btn-group-toggle" ngbRadioGroup name="radioBasic" formControlName="dateSpan" <div class="btn-group btn-group-toggle" ngbRadioGroup name="radioBasic" formControlName="dateSpan">
(change)="onChangeWindowPreference($event)">
<label ngbButtonLabel class="btn-primary btn-sm"> <label ngbButtonLabel class="btn-primary btn-sm">
<input ngbButton type="radio" [value]="'1d'" [routerLink]="['/pools' | relativeUrl]" fragment="1d"> 1D <input ngbButton type="radio" [value]="'1d'" [routerLink]="['/pools' | relativeUrl]" fragment="1d"> 1D
</label> </label>
@ -54,7 +53,7 @@
<th i18n="latest-blocks.mined">Block Count (%)</th> <th i18n="latest-blocks.mined">Block Count (%)</th>
<th class="d-none d-md-block" i18n="latest-blocks.transactions">Empty Blocks (%)</th> <th class="d-none d-md-block" i18n="latest-blocks.transactions">Empty Blocks (%)</th>
</thead> </thead>
<tbody *ngIf="(miningStatsEmitter$ | async) as miningStats"> <tbody *ngIf="(miningStatsObservable$ | async) as miningStats">
<tr> <tr>
<td class="d-none d-md-block">-</td> <td class="d-none d-md-block">-</td>
<td><!-- LOGO --></td> <td><!-- LOGO --></td>

View File

@ -1,10 +1,11 @@
import { Component, OnDestroy, OnInit } from '@angular/core'; import { Component, OnDestroy, OnInit } from '@angular/core';
import { FormBuilder, FormGroup } from '@angular/forms'; import { FormBuilder, FormGroup } from '@angular/forms';
import { EChartsOption } from 'echarts'; import { EChartsOption } from 'echarts';
import { BehaviorSubject, Subscription } from 'rxjs'; import { combineLatest, Observable, of } from 'rxjs';
import { StateService } from 'src/app/services/state.service'; import { catchError, skip, startWith, switchMap, tap } from 'rxjs/operators';
import { StorageService } from 'src/app/services/storage.service'; import { StorageService } from '../..//services/storage.service';
import { MiningService, MiningStats } from '../../services/mining.service'; import { MiningService, MiningStats } from '../../services/mining.service';
import { StateService } from '../../services/state.service';
@Component({ @Component({
selector: 'app-pool-ranking', selector: 'app-pool-ranking',
@ -22,79 +23,74 @@ export class PoolRankingComponent implements OnInit, OnDestroy {
poolsWindowPreference: string; poolsWindowPreference: string;
radioGroupForm: FormGroup; radioGroupForm: FormGroup;
miningStats!: MiningStats;
miningStatsEmitter$ = new BehaviorSubject<MiningStats>(this.miningStats);
blocksSubscription: Subscription;
miningSubscription: Subscription;
isLoading = true; isLoading = true;
chartOptions: EChartsOption = {}; chartOptions: EChartsOption = {};
chartInitOptions = { chartInitOptions = {
renderer: 'svg' renderer: 'svg'
}; };
miningStatsObservable$: Observable<MiningStats>;
constructor( constructor(
private stateService: StateService, private stateService: StateService,
private storageService: StorageService, private storageService: StorageService,
private formBuilder: FormBuilder, private formBuilder: FormBuilder,
private miningService: MiningService, private miningService: MiningService,
) { ) {
this.poolsWindowPreference = this.storageService.getValue('poolsWindowPreference') ? this.storageService.getValue('poolsWindowPreference').trim() : '2h'; this.poolsWindowPreference = this.storageService.getValue('poolsWindowPreference') ? this.storageService.getValue('poolsWindowPreference') : '1d';
this.radioGroupForm = this.formBuilder.group({ dateSpan: this.poolsWindowPreference }); this.radioGroupForm = this.formBuilder.group({ dateSpan: this.poolsWindowPreference });
this.radioGroupForm.controls.dateSpan.setValue(this.poolsWindowPreference); this.radioGroupForm.controls.dateSpan.setValue(this.poolsWindowPreference);
} }
ngOnInit(): void { ngOnInit(): void {
this.refreshMiningStats(); // When...
this.watchBlocks(); this.miningStatsObservable$ = combineLatest([
// ...a new block is mined
this.stateService.blocks$
.pipe(
// (we always receives some blocks at start so only trigger for the last one)
skip(this.stateService.env.MEMPOOL_BLOCKS_AMOUNT - 1),
),
// ...or we change the timespan
this.radioGroupForm.get('dateSpan').valueChanges
.pipe(
startWith(this.poolsWindowPreference), // (trigger when the page loads)
tap((value) => {
this.storageService.setValue('poolsWindowPreference', value);
this.poolsWindowPreference = value;
})
)
])
// ...then refresh the mining stats
.pipe(
switchMap(() => {
this.isLoading = true;
return this.miningService.getMiningStats(this.getSQLInterval(this.poolsWindowPreference))
.pipe(
catchError((e) => of(this.getEmptyMiningStat()))
);
}),
tap(data => {
this.isLoading = false;
this.prepareChartOptions(data);
})
);
} }
ngOnDestroy(): void { ngOnDestroy(): void {
this.blocksSubscription.unsubscribe();
this.miningSubscription.unsubscribe();
} }
refreshMiningStats() { generatePoolsChartSerieData(miningStats) {
this.miningSubscription = this.miningService.getMiningStats(this.getSQLInterval(this.poolsWindowPreference))
.subscribe(async data => {
this.miningStats = data;
this.miningStatsEmitter$.next(this.miningStats);
this.prepareChartOptions();
this.isLoading = false;
});
return this.miningSubscription;
}
watchBlocks() {
this.blocksSubscription = this.stateService.blocks$
.subscribe(() => {
if (!this.miningStats) {
return;
}
this.refreshMiningStats();
});
}
onChangeWindowPreference(e) {
this.storageService.setValue('poolsWindowPreference', e.target.value);
this.poolsWindowPreference = e.target.value;
this.isLoading = true;
this.refreshMiningStats();
}
generatePoolsChartSerieData() {
const poolShareThreshold = 0.5; // Do not draw pools which hashrate share is lower than that const poolShareThreshold = 0.5; // Do not draw pools which hashrate share is lower than that
const data: object[] = []; const data: object[] = [];
this.miningStats.pools.forEach((pool) => { miningStats.pools.forEach((pool) => {
if (parseFloat(pool.share) < poolShareThreshold) { if (parseFloat(pool.share) < poolShareThreshold) {
return; return;
} }
data.push({ data.push({
value: pool.share, value: pool.share,
name: pool.name, name: pool.name + ` (${pool.share}%)`,
label: { color: '#FFFFFF' }, label: { color: '#FFFFFF' },
tooltip: { tooltip: {
formatter: () => { formatter: () => {
@ -113,7 +109,7 @@ export class PoolRankingComponent implements OnInit, OnDestroy {
return data; return data;
} }
prepareChartOptions() { prepareChartOptions(miningStats) {
this.chartOptions = { this.chartOptions = {
title: { title: {
text: (this.poolsWindowPreference === '1d') ? 'Hashrate distribution' : 'Block distribution', text: (this.poolsWindowPreference === '1d') ? 'Hashrate distribution' : 'Block distribution',
@ -143,7 +139,7 @@ export class PoolRankingComponent implements OnInit, OnDestroy {
name: 'Mining pool', name: 'Mining pool',
type: 'pie', type: 'pie',
radius: ['30%', '70%'], radius: ['30%', '70%'],
data: this.generatePoolsChartSerieData(), data: this.generatePoolsChartSerieData(miningStats),
labelLine: { labelLine: {
lineStyle: { lineStyle: {
width: 2, width: 2,
@ -193,5 +189,21 @@ export class PoolRankingComponent implements OnInit, OnDestroy {
} }
} }
/**
* Default mining stats if something goes wrong
*/
getEmptyMiningStat() {
return {
lastEstimatedHashrate: 'Error',
blockCount: 0,
totalEmptyBlock: 0,
totalEmptyBlockRatio: '',
pools: [],
miningUnits: {
hashrateDivider: 1,
hashrateUnit: '',
},
};
}
} }

View File

@ -53,22 +53,22 @@ export interface LiquidPegs {
export interface ITranslators { [language: string]: string; } export interface ITranslators { [language: string]: string; }
export interface SinglePoolStats { export interface SinglePoolStats {
pooldId: number, pooldId: number;
name: string, name: string;
link: string, link: string;
blockCount: number, blockCount: number;
emptyBlocks: number, emptyBlocks: number;
rank: number, rank: number;
share: string, share: string;
lastEstimatedHashrate: string, lastEstimatedHashrate: string;
emptyBlockRatio: string, emptyBlockRatio: string;
logo: string, logo: string;
} }
export interface PoolsStats { export interface PoolsStats {
blockCount: number, blockCount: number;
lastEstimatedHashrate: number, lastEstimatedHashrate: number;
pools: SinglePoolStats[], pools: SinglePoolStats[];
} }
export interface ITranslators { [language: string]: string; } export interface ITranslators { [language: string]: string; }

View File

@ -6,17 +6,17 @@ import { ApiService } from '../services/api.service';
import { StateService } from './state.service'; import { StateService } from './state.service';
export interface MiningUnits { export interface MiningUnits {
hashrateDivider: number, hashrateDivider: number;
hashrateUnit: string, hashrateUnit: string;
} }
export interface MiningStats { export interface MiningStats {
lastEstimatedHashrate: string, lastEstimatedHashrate: string;
blockCount: number, blockCount: number;
totalEmptyBlock: number, totalEmptyBlock: number;
totalEmptyBlockRatio: string, totalEmptyBlockRatio: string;
pools: SinglePoolStats[], pools: SinglePoolStats[];
miningUnits: MiningUnits, miningUnits: MiningUnits;
} }
@Injectable({ @Injectable({
@ -38,15 +38,15 @@ export class MiningService {
/** /**
* Set the hashrate power of ten we want to display * Set the hashrate power of ten we want to display
*/ */
public getMiningUnits() : MiningUnits { public getMiningUnits(): MiningUnits {
const powerTable = { const powerTable = {
0: "H/s", 0: 'H/s',
3: "kH/s", 3: 'kH/s',
6: "MH/s", 6: 'MH/s',
9: "GH/s", 9: 'GH/s',
12: "TH/s", 12: 'TH/s',
15: "PH/s", 15: 'PH/s',
18: "EH/s", 18: 'EH/s',
}; };
// I think it's fine to hardcode this since we don't have x1000 hashrate jump everyday // I think it's fine to hardcode this since we don't have x1000 hashrate jump everyday
@ -62,7 +62,7 @@ export class MiningService {
}; };
} }
private generateMiningStats(stats: PoolsStats) : MiningStats { private generateMiningStats(stats: PoolsStats): MiningStats {
const miningUnits = this.getMiningUnits(); const miningUnits = this.getMiningUnits();
const hashrateDivider = miningUnits.hashrateDivider; const hashrateDivider = miningUnits.hashrateDivider;
@ -77,7 +77,7 @@ export class MiningService {
emptyBlockRatio: (poolStat.emptyBlocks / poolStat.blockCount * 100).toFixed(2), emptyBlockRatio: (poolStat.emptyBlocks / poolStat.blockCount * 100).toFixed(2),
logo: `./resources/mining-pools/` + poolStat.name.toLowerCase().replace(' ', '').replace('.', '') + '.svg', logo: `./resources/mining-pools/` + poolStat.name.toLowerCase().replace(' ', '').replace('.', '') + '.svg',
...poolStat ...poolStat
} };
}); });
return { return {