Improve mining graphs timespan selection UX

This commit is contained in:
nymkappa 2022-04-11 18:17:36 +09:00
parent 848e02eca0
commit 7dd0173e84
No known key found for this signature in database
GPG Key ID: E155910B16E8BD04
13 changed files with 158 additions and 97 deletions

View File

@ -27,6 +27,7 @@ class Mining {
case '3m': timeRange = 7200; break; // 2h case '3m': timeRange = 7200; break; // 2h
case '1m': timeRange = 1800; break; // 30min case '1m': timeRange = 1800; break; // 30min
case '1w': timeRange = 300; break; // 5min case '1w': timeRange = 300; break; // 5min
case '3d': timeRange = 1; break;
case '24h': timeRange = 1; break; case '24h': timeRange = 1; break;
default: timeRange = 86400; break; // 24h default: timeRange = 86400; break; // 24h
} }

View File

@ -1,41 +1,43 @@
<div [class]="widget === false ? 'full-container' : ''"> <div class="full-container">
<div class="card-header mb-0 mb-md-4">
<div class="card-header mb-0 mb-md-4" [style]="widget ? 'display:none' : ''">
<span i18n="mining.block-fees">Block fees</span> <span i18n="mining.block-fees">Block fees</span>
<form [formGroup]="radioGroupForm" class="formRadioGroup" *ngIf="(statsObservable$ | async) as hashrates"> <form [formGroup]="radioGroupForm" class="formRadioGroup" *ngIf="(statsObservable$ | async) as stats">
<div class="btn-group btn-group-toggle" ngbRadioGroup name="radioBasic" formControlName="dateSpan"> <div class="btn-group btn-group-toggle" ngbRadioGroup name="radioBasic" formControlName="dateSpan">
<label ngbButtonLabel class="btn-primary btn-sm" *ngIf="hashrates.availableTimespanDay >= 1"> <label ngbButtonLabel class="btn-primary btn-sm" *ngIf="stats.availableTimespanDay >= 1">
<input ngbButton type="radio" [value]="'24h'"> 24h <input ngbButton type="radio" [value]="'24h'" fragment="24h"> 24h
</label> </label>
<label ngbButtonLabel class="btn-primary btn-sm" *ngIf="hashrates.availableTimespanDay >= 7"> <label ngbButtonLabel class="btn-primary btn-sm" *ngIf="stats.availableTimespanDay >= 3">
<input ngbButton type="radio" [value]="'1w'"> 1W <input ngbButton type="radio" [value]="'3d'" fragment="3d"> 3D
</label> </label>
<label ngbButtonLabel class="btn-primary btn-sm" *ngIf="hashrates.availableTimespanDay >= 30"> <label ngbButtonLabel class="btn-primary btn-sm" *ngIf="stats.availableTimespanDay >= 7">
<input ngbButton type="radio" [value]="'1m'"> 1M <input ngbButton type="radio" [value]="'1w'" fragment="1w"> 1W
</label> </label>
<label ngbButtonLabel class="btn-primary btn-sm" *ngIf="hashrates.availableTimespanDay >= 90"> <label ngbButtonLabel class="btn-primary btn-sm" *ngIf="stats.availableTimespanDay >= 30">
<input ngbButton type="radio" [value]="'3m'"> 3M <input ngbButton type="radio" [value]="'1m'" fragment="1m"> 1M
</label> </label>
<label ngbButtonLabel class="btn-primary btn-sm" *ngIf="hashrates.availableTimespanDay >= 180"> <label ngbButtonLabel class="btn-primary btn-sm" *ngIf="stats.availableTimespanDay >= 90">
<input ngbButton type="radio" [value]="'6m'"> 6M <input ngbButton type="radio" [value]="'3m'" fragment="3m"> 3M
</label> </label>
<label ngbButtonLabel class="btn-primary btn-sm" *ngIf="hashrates.availableTimespanDay >= 365"> <label ngbButtonLabel class="btn-primary btn-sm" *ngIf="stats.availableTimespanDay >= 180">
<input ngbButton type="radio" [value]="'1y'"> 1Y <input ngbButton type="radio" [value]="'6m'" fragment="6m"> 6M
</label> </label>
<label ngbButtonLabel class="btn-primary btn-sm" *ngIf="hashrates.availableTimespanDay >= 730"> <label ngbButtonLabel class="btn-primary btn-sm" *ngIf="stats.availableTimespanDay >= 365">
<input ngbButton type="radio" [value]="'2y'"> 2Y <input ngbButton type="radio" [value]="'1y'" fragment="1y"> 1Y
</label> </label>
<label ngbButtonLabel class="btn-primary btn-sm" *ngIf="hashrates.availableTimespanDay >= 1095"> <label ngbButtonLabel class="btn-primary btn-sm" *ngIf="stats.availableTimespanDay >= 730">
<input ngbButton type="radio" [value]="'3y'"> 3Y <input ngbButton type="radio" [value]="'2y'" fragment="2y"> 2Y
</label> </label>
<label ngbButtonLabel class="btn-primary btn-sm"> <label ngbButtonLabel class="btn-primary btn-sm" *ngIf="stats.availableTimespanDay >= 1095">
<input ngbButton type="radio" [value]="'all'"> ALL <input ngbButton type="radio" [value]="'3y'" fragment="3y"> 3Y
</label>
<label ngbButtonLabel class="btn-primary btn-sm" *ngIf="stats.availableTimespanDay > 1095">
<input ngbButton type="radio" [value]="'all'" fragment="all"> ALL
</label> </label>
</div> </div>
</form> </form>
</div> </div>
<div [class]="!widget ? 'chart' : 'chart-widget'" echarts [initOpts]="chartInitOptions" [options]="chartOptions"> <div class="chart" echarts [initOpts]="chartInitOptions" [options]="chartOptions">
</div> </div>
<div class="text-center loadingGraphs" *ngIf="isLoading"> <div class="text-center loadingGraphs" *ngIf="isLoading">
<div class="spinner-border text-light"></div> <div class="spinner-border text-light"></div>

View File

@ -7,6 +7,8 @@ import { SeoService } from 'src/app/services/seo.service';
import { formatNumber } from '@angular/common'; import { formatNumber } from '@angular/common';
import { FormBuilder, FormGroup } from '@angular/forms'; import { FormBuilder, FormGroup } from '@angular/forms';
import { formatterXAxisLabel } from 'src/app/shared/graphs.utils'; import { formatterXAxisLabel } from 'src/app/shared/graphs.utils';
import { StorageService } from 'src/app/services/storage.service';
import { MiningService } from 'src/app/services/mining.service';
@Component({ @Component({
selector: 'app-block-fees-graph', selector: 'app-block-fees-graph',
@ -24,10 +26,10 @@ import { formatterXAxisLabel } from 'src/app/shared/graphs.utils';
}) })
export class BlockFeesGraphComponent implements OnInit { export class BlockFeesGraphComponent implements OnInit {
@Input() tableOnly = false; @Input() tableOnly = false;
@Input() widget = false;
@Input() right: number | string = 45; @Input() right: number | string = 45;
@Input() left: number | string = 75; @Input() left: number | string = 75;
miningWindowPreference: string;
radioGroupForm: FormGroup; radioGroupForm: FormGroup;
chartOptions: EChartsOption = {}; chartOptions: EChartsOption = {};
@ -44,21 +46,25 @@ export class BlockFeesGraphComponent implements OnInit {
@Inject(LOCALE_ID) public locale: string, @Inject(LOCALE_ID) public locale: string,
private seoService: SeoService, private seoService: SeoService,
private apiService: ApiService, private apiService: ApiService,
private formBuilder: FormBuilder private formBuilder: FormBuilder,
private storageService: StorageService,
private miningService: MiningService
) { ) {
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');
} }
ngOnInit(): void { ngOnInit(): void {
if (!this.widget) {
this.seoService.setTitle($localize`:@@mining.block-fees:Block Fees`); this.seoService.setTitle($localize`:@@mining.block-fees:Block Fees`);
} this.miningWindowPreference = this.miningService.getDefaultTimespan('24h');
this.radioGroupForm = this.formBuilder.group({ dateSpan: this.miningWindowPreference });
this.radioGroupForm.controls.dateSpan.setValue(this.miningWindowPreference);
this.statsObservable$ = this.radioGroupForm.get('dateSpan').valueChanges this.statsObservable$ = this.radioGroupForm.get('dateSpan').valueChanges
.pipe( .pipe(
startWith('1y'), startWith(this.miningWindowPreference),
switchMap((timespan) => { switchMap((timespan) => {
this.storageService.setValue('miningWindowPreference', timespan);
this.timespan = timespan; this.timespan = timespan;
this.isLoading = true; this.isLoading = true;
return this.apiService.getHistoricalBlockFees$(timespan) return this.apiService.getHistoricalBlockFees$(timespan)
@ -103,7 +109,7 @@ export class BlockFeesGraphComponent implements OnInit {
left: this.left, left: this.left,
}, },
tooltip: { tooltip: {
show: !this.isMobile() || !this.widget, show: !this.isMobile(),
trigger: 'axis', trigger: 'axis',
axisPointer: { axisPointer: {
type: 'line' type: 'line'
@ -161,7 +167,7 @@ export class BlockFeesGraphComponent implements OnInit {
}, },
}, },
], ],
dataZoom: this.widget ? null : [{ dataZoom: [{
type: 'inside', type: 'inside',
realtime: true, realtime: true,
zoomLock: true, zoomLock: true,

View File

@ -20,25 +20,28 @@
<div class="card-header mb-0 mb-md-4" [style]="widget ? 'display:none' : ''"> <div class="card-header mb-0 mb-md-4" [style]="widget ? 'display:none' : ''">
<span i18n="mining.hashrate-difficulty">Hashrate & Difficulty</span> <span i18n="mining.hashrate-difficulty">Hashrate & Difficulty</span>
<form [formGroup]="radioGroupForm" class="formRadioGroup" *ngIf="(hashrateObservable$ | async) as hashrates"> <form [formGroup]="radioGroupForm" class="formRadioGroup" *ngIf="(hashrateObservable$ | async) as stats">
<div class="btn-group btn-group-toggle" ngbRadioGroup name="radioBasic" formControlName="dateSpan"> <div class="btn-group btn-group-toggle" ngbRadioGroup name="radioBasic" formControlName="dateSpan">
<label ngbButtonLabel class="btn-primary btn-sm" *ngIf="hashrates.availableTimespanDay >= 90"> <label ngbButtonLabel class="btn-primary btn-sm" *ngIf="stats.availableTimespanDay >= 30">
<input ngbButton type="radio" [value]="'3m'"> 3M <input ngbButton type="radio" [value]="'1m'" fragment="1m"> 1M
</label> </label>
<label ngbButtonLabel class="btn-primary btn-sm" *ngIf="hashrates.availableTimespanDay >= 180"> <label ngbButtonLabel class="btn-primary btn-sm" *ngIf="stats.availableTimespanDay >= 90">
<input ngbButton type="radio" [value]="'6m'"> 6M <input ngbButton type="radio" [value]="'3m'" fragment="3m"> 3M
</label> </label>
<label ngbButtonLabel class="btn-primary btn-sm" *ngIf="hashrates.availableTimespanDay >= 365"> <label ngbButtonLabel class="btn-primary btn-sm" *ngIf="stats.availableTimespanDay >= 180">
<input ngbButton type="radio" [value]="'1y'"> 1Y <input ngbButton type="radio" [value]="'6m'" fragment="6m"> 6M
</label> </label>
<label ngbButtonLabel class="btn-primary btn-sm" *ngIf="hashrates.availableTimespanDay >= 730"> <label ngbButtonLabel class="btn-primary btn-sm" *ngIf="stats.availableTimespanDay >= 365">
<input ngbButton type="radio" [value]="'2y'"> 2Y <input ngbButton type="radio" [value]="'1y'" fragment="1y"> 1Y
</label> </label>
<label ngbButtonLabel class="btn-primary btn-sm" *ngIf="hashrates.availableTimespanDay >= 1095"> <label ngbButtonLabel class="btn-primary btn-sm" *ngIf="stats.availableTimespanDay >= 730">
<input ngbButton type="radio" [value]="'3y'"> 3Y <input ngbButton type="radio" [value]="'2y'" fragment="2y"> 2Y
</label> </label>
<label ngbButtonLabel class="btn-primary btn-sm"> <label ngbButtonLabel class="btn-primary btn-sm" *ngIf="stats.availableTimespanDay >= 1095">
<input ngbButton type="radio" [value]="'all'"> ALL <input ngbButton type="radio" [value]="'3y'" fragment="3y"> 3Y
</label>
<label ngbButtonLabel class="btn-primary btn-sm" *ngIf="stats.availableTimespanDay > 1095">
<input ngbButton type="radio" [value]="'all'" fragment="all"> ALL
</label> </label>
</div> </div>
</form> </form>

View File

@ -7,6 +7,8 @@ import { SeoService } from 'src/app/services/seo.service';
import { formatNumber } from '@angular/common'; import { formatNumber } from '@angular/common';
import { FormBuilder, FormGroup } from '@angular/forms'; import { FormBuilder, FormGroup } from '@angular/forms';
import { selectPowerOfTen } from 'src/app/bitcoin.utils'; import { selectPowerOfTen } from 'src/app/bitcoin.utils';
import { StorageService } from 'src/app/services/storage.service';
import { MiningService } from 'src/app/services/mining.service';
@Component({ @Component({
selector: 'app-hashrate-chart', selector: 'app-hashrate-chart',
@ -28,6 +30,7 @@ export class HashrateChartComponent implements OnInit {
@Input() right: number | string = 45; @Input() right: number | string = 45;
@Input() left: number | string = 75; @Input() left: number | string = 75;
miningWindowPreference: string;
radioGroupForm: FormGroup; radioGroupForm: FormGroup;
chartOptions: EChartsOption = {}; chartOptions: EChartsOption = {};
@ -47,20 +50,32 @@ export class HashrateChartComponent implements OnInit {
private apiService: ApiService, private apiService: ApiService,
private formBuilder: FormBuilder, private formBuilder: FormBuilder,
private cd: ChangeDetectorRef, private cd: ChangeDetectorRef,
private storageService: StorageService,
private miningService: MiningService
) { ) {
this.radioGroupForm = this.formBuilder.group({ dateSpan: '1y' });
this.radioGroupForm.controls.dateSpan.setValue('1y');
} }
ngOnInit(): void { ngOnInit(): void {
if (!this.widget) { let firstRun = true;
if (this.widget) {
this.miningWindowPreference = '1y';
} else {
this.seoService.setTitle($localize`:@@mining.hashrate-difficulty:Hashrate and Difficulty`); this.seoService.setTitle($localize`:@@mining.hashrate-difficulty:Hashrate and Difficulty`);
this.miningWindowPreference = this.miningService.getDefaultTimespan('1m');
} }
this.radioGroupForm = this.formBuilder.group({ dateSpan: this.miningWindowPreference });
this.radioGroupForm.controls.dateSpan.setValue(this.miningWindowPreference);
this.hashrateObservable$ = this.radioGroupForm.get('dateSpan').valueChanges this.hashrateObservable$ = this.radioGroupForm.get('dateSpan').valueChanges
.pipe( .pipe(
startWith('1y'), startWith(this.miningWindowPreference),
switchMap((timespan) => { switchMap((timespan) => {
if (!this.widget && !firstRun) {
this.storageService.setValue('miningWindowPreference', timespan);
}
firstRun = false;
this.miningWindowPreference = timespan;
this.isLoading = true; this.isLoading = true;
return this.apiService.getHistoricalHashrate$(timespan) return this.apiService.getHistoricalHashrate$(timespan)
.pipe( .pipe(

View File

@ -1,32 +1,35 @@
<div [class]="widget === false ? 'full-container' : ''"> <div class="full-container">
<div class="card-header mb-0 mb-md-4" [style]="widget ? 'display:none' : ''"> <div class="card-header mb-0 mb-md-4">
<span *ngIf="!widget" i18n="mining.pools-dominance">Mining pools dominance</span> <span i18n="mining.pools-dominance">Mining pools dominance</span>
<form [formGroup]="radioGroupForm" class="formRadioGroup" *ngIf="(hashrateObservable$ | async) as hashrates"> <form [formGroup]="radioGroupForm" class="formRadioGroup" *ngIf="(hashrateObservable$ | async) as stats">
<div class="btn-group btn-group-toggle" ngbRadioGroup name="radioBasic" formControlName="dateSpan"> <div class="btn-group btn-group-toggle" ngbRadioGroup name="radioBasic" formControlName="dateSpan">
<label ngbButtonLabel class="btn-primary btn-sm" *ngIf="hashrates.availableTimespanDay >= 90"> <label ngbButtonLabel class="btn-primary btn-sm" *ngIf="stats.availableTimespanDay >= 30">
<input ngbButton type="radio" [value]="'3m'"> 3M <input ngbButton type="radio" [value]="'1m'" fragment="1m"> 1M
</label> </label>
<label ngbButtonLabel class="btn-primary btn-sm" *ngIf="hashrates.availableTimespanDay >= 180"> <label ngbButtonLabel class="btn-primary btn-sm" *ngIf="stats.availableTimespanDay >= 90">
<input ngbButton type="radio" [value]="'6m'"> 6M <input ngbButton type="radio" [value]="'3m'" fragment="3m"> 3M
</label> </label>
<label ngbButtonLabel class="btn-primary btn-sm" *ngIf="hashrates.availableTimespanDay >= 365"> <label ngbButtonLabel class="btn-primary btn-sm" *ngIf="stats.availableTimespanDay >= 180">
<input ngbButton type="radio" [value]="'1y'"> 1Y <input ngbButton type="radio" [value]="'6m'" fragment="6m"> 6M
</label> </label>
<label ngbButtonLabel class="btn-primary btn-sm" *ngIf="hashrates.availableTimespanDay >= 730"> <label ngbButtonLabel class="btn-primary btn-sm" *ngIf="stats.availableTimespanDay >= 365">
<input ngbButton type="radio" [value]="'2y'"> 2Y <input ngbButton type="radio" [value]="'1y'" fragment="1y"> 1Y
</label> </label>
<label ngbButtonLabel class="btn-primary btn-sm" *ngIf="hashrates.availableTimespanDay >= 1095"> <label ngbButtonLabel class="btn-primary btn-sm" *ngIf="stats.availableTimespanDay >= 730">
<input ngbButton type="radio" [value]="'3y'"> 3Y <input ngbButton type="radio" [value]="'2y'" fragment="2y"> 2Y
</label> </label>
<label ngbButtonLabel class="btn-primary btn-sm"> <label ngbButtonLabel class="btn-primary btn-sm" *ngIf="stats.availableTimespanDay >= 1095">
<input ngbButton type="radio" [value]="'all'"> ALL <input ngbButton type="radio" [value]="'3y'" fragment="3y"> 3Y
</label>
<label ngbButtonLabel class="btn-primary btn-sm" *ngIf="stats.availableTimespanDay > 1095">
<input ngbButton type="radio" [value]="'all'" fragment="all"> ALL
</label> </label>
</div> </div>
</form> </form>
</div> </div>
<div [class]="!widget ? 'chart' : 'chart-widget'" <div class="chart"
echarts [initOpts]="chartInitOptions" [options]="chartOptions"></div> echarts [initOpts]="chartInitOptions" [options]="chartOptions"></div>
<div class="text-center loadingGraphs" *ngIf="isLoading"> <div class="text-center loadingGraphs" *ngIf="isLoading">
<div class="spinner-border text-light"></div> <div class="spinner-border text-light"></div>

View File

@ -6,6 +6,8 @@ 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 { FormBuilder, FormGroup } from '@angular/forms'; import { FormBuilder, FormGroup } from '@angular/forms';
import { poolsColor } from 'src/app/app.constants'; import { poolsColor } from 'src/app/app.constants';
import { StorageService } from 'src/app/services/storage.service';
import { MiningService } from 'src/app/services/mining.service';
@Component({ @Component({
selector: 'app-hashrate-chart-pools', selector: 'app-hashrate-chart-pools',
@ -22,10 +24,10 @@ import { poolsColor } from 'src/app/app.constants';
changeDetection: ChangeDetectionStrategy.OnPush, changeDetection: ChangeDetectionStrategy.OnPush,
}) })
export class HashrateChartPoolsComponent implements OnInit { export class HashrateChartPoolsComponent implements OnInit {
@Input() widget = false;
@Input() right: number | string = 45; @Input() right: number | string = 45;
@Input() left: number | string = 25; @Input() left: number | string = 25;
miningWindowPreference: string;
radioGroupForm: FormGroup; radioGroupForm: FormGroup;
chartOptions: EChartsOption = {}; chartOptions: EChartsOption = {};
@ -44,20 +46,29 @@ export class HashrateChartPoolsComponent implements OnInit {
private apiService: ApiService, private apiService: ApiService,
private formBuilder: FormBuilder, private formBuilder: FormBuilder,
private cd: ChangeDetectorRef, private cd: ChangeDetectorRef,
private storageService: StorageService,
private miningService: MiningService
) { ) {
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');
} }
ngOnInit(): void { ngOnInit(): void {
if (!this.widget) { let firstRun = true;
this.seoService.setTitle($localize`:@@mining.pools-historical-dominance:Pools Historical Dominance`); this.seoService.setTitle($localize`:@@mining.pools-historical-dominance:Pools Historical Dominance`);
} this.miningWindowPreference = this.miningService.getDefaultTimespan('1m');
this.radioGroupForm = this.formBuilder.group({ dateSpan: this.miningWindowPreference });
this.radioGroupForm.controls.dateSpan.setValue(this.miningWindowPreference);
this.hashrateObservable$ = this.radioGroupForm.get('dateSpan').valueChanges this.hashrateObservable$ = this.radioGroupForm.get('dateSpan').valueChanges
.pipe( .pipe(
startWith('1y'), startWith(this.miningWindowPreference),
switchMap((timespan) => { switchMap((timespan) => {
if (!firstRun) {
this.storageService.setValue('miningWindowPreference', timespan);
}
firstRun = false;
this.isLoading = true; this.isLoading = true;
return this.apiService.getHistoricalPoolsHashrate$(timespan) return this.apiService.getHistoricalPoolsHashrate$(timespan)
.pipe( .pipe(
@ -157,11 +168,11 @@ export class HashrateChartPoolsComponent implements OnInit {
grid: { grid: {
right: this.right, right: this.right,
left: this.left, left: this.left,
bottom: this.widget ? 30 : 70, bottom: 70,
top: this.widget || this.isMobile() ? 10 : 50, top: this.isMobile() ? 10 : 50,
}, },
tooltip: { tooltip: {
show: !this.isMobile() || !this.widget, show: !this.isMobile(),
trigger: 'axis', trigger: 'axis',
axisPointer: { axisPointer: {
type: 'line' type: 'line'
@ -188,9 +199,9 @@ export class HashrateChartPoolsComponent implements OnInit {
}, },
xAxis: data.series.length === 0 ? undefined : { xAxis: data.series.length === 0 ? undefined : {
type: 'time', type: 'time',
splitNumber: (this.isMobile() || this.widget) ? 5 : 10, splitNumber: (this.isMobile()) ? 5 : 10,
}, },
legend: (this.isMobile() || this.widget || data.series.length === 0) ? undefined : { legend: (this.isMobile() || data.series.length === 0) ? undefined : {
data: data.legends data: data.legends
}, },
yAxis: data.series.length === 0 ? undefined : { yAxis: data.series.length === 0 ? undefined : {
@ -207,7 +218,7 @@ export class HashrateChartPoolsComponent implements OnInit {
min: 0, min: 0,
}, },
series: data.series, series: data.series,
dataZoom: this.widget ? null : [{ dataZoom: [{
type: 'inside', type: 'inside',
realtime: true, realtime: true,
zoomLock: true, zoomLock: true,

View File

@ -55,7 +55,7 @@
<label ngbButtonLabel class="btn-primary btn-sm" *ngIf="miningStats.availableTimespanDay >= 1095"> <label ngbButtonLabel class="btn-primary btn-sm" *ngIf="miningStats.availableTimespanDay >= 1095">
<input ngbButton type="radio" [value]="'3y'" fragment="3y"> 3Y <input ngbButton type="radio" [value]="'3y'" fragment="3y"> 3Y
</label> </label>
<label ngbButtonLabel class="btn-primary btn-sm"> <label ngbButtonLabel class="btn-primary btn-sm" *ngIf="miningStats.availableTimespanDay > 1095">
<input ngbButton type="radio" [value]="'all'" fragment="all"> ALL <input ngbButton type="radio" [value]="'all'" fragment="all"> ALL
</label> </label>
</div> </div>
@ -79,7 +79,7 @@
<th class="d-none d-md-block" i18n="mining.rank">Rank</th> <th class="d-none d-md-block" i18n="mining.rank">Rank</th>
<th class=""></th> <th class=""></th>
<th class="" i18n="mining.pool-name">Pool</th> <th class="" i18n="mining.pool-name">Pool</th>
<th class="" *ngIf="this.poolsWindowPreference === '24h'" i18n="mining.hashrate">Hashrate</th> <th class="" *ngIf="this.miningWindowPreference === '24h'" i18n="mining.hashrate">Hashrate</th>
<th class="" i18n="master-page.blocks">Blocks</th> <th class="" i18n="master-page.blocks">Blocks</th>
<th class="d-none d-md-block" i18n="mining.empty-blocks">Empty Blocks</th> <th class="d-none d-md-block" i18n="mining.empty-blocks">Empty Blocks</th>
</tr> </tr>
@ -90,7 +90,7 @@
<td class="text-right"><img width="25" height="25" src="{{ pool.logo }}" <td class="text-right"><img width="25" height="25" src="{{ pool.logo }}"
onError="this.src = './resources/mining-pools/default.svg'"></td> onError="this.src = './resources/mining-pools/default.svg'"></td>
<td class=""><a [routerLink]="[('/mining/pool/' + pool.slug) | relativeUrl]">{{ pool.name }}</a></td> <td class=""><a [routerLink]="[('/mining/pool/' + pool.slug) | relativeUrl]">{{ pool.name }}</a></td>
<td class="" *ngIf="this.poolsWindowPreference === '24h' && !isLoading">{{ pool.lastEstimatedHashrate }} {{ <td class="" *ngIf="this.miningWindowPreference === '24h' && !isLoading">{{ pool.lastEstimatedHashrate }} {{
miningStats.miningUnits.hashrateUnit }}</td> miningStats.miningUnits.hashrateUnit }}</td>
<td class="">{{ pool['blockText'] }}</td> <td class="">{{ pool['blockText'] }}</td>
<td class="d-none d-md-block">{{ pool.emptyBlocks }} ({{ pool.emptyBlockRatio }}%)</td> <td class="d-none d-md-block">{{ pool.emptyBlocks }} ({{ pool.emptyBlockRatio }}%)</td>
@ -99,7 +99,7 @@
<td class="d-none d-md-block"></td> <td class="d-none d-md-block"></td>
<td class="text-right"></td> <td class="text-right"></td>
<td class="" i18n="mining.all-miners"><b>All miners</b></td> <td class="" i18n="mining.all-miners"><b>All miners</b></td>
<td class="" *ngIf="this.poolsWindowPreference === '24h'"><b>{{ miningStats.lastEstimatedHashrate}} {{ <td class="" *ngIf="this.miningWindowPreference === '24h'"><b>{{ miningStats.lastEstimatedHashrate}} {{
miningStats.miningUnits.hashrateUnit }}</b></td> miningStats.miningUnits.hashrateUnit }}</b></td>
<td class=""><b>{{ miningStats.blockCount }}</b></td> <td class=""><b>{{ miningStats.blockCount }}</b></td>
<td class="d-none d-md-block"><b>{{ miningStats.totalEmptyBlock }} ({{ miningStats.totalEmptyBlockRatio <td class="d-none d-md-block"><b>{{ miningStats.totalEmptyBlock }} ({{ miningStats.totalEmptyBlockRatio

View File

@ -19,9 +19,9 @@ import { RelativeUrlPipe } from 'src/app/shared/pipes/relative-url/relative-url.
changeDetection: ChangeDetectionStrategy.OnPush, changeDetection: ChangeDetectionStrategy.OnPush,
}) })
export class PoolRankingComponent implements OnInit { export class PoolRankingComponent implements OnInit {
@Input() widget: boolean = false; @Input() widget = false;
poolsWindowPreference: string; miningWindowPreference: string;
radioGroupForm: FormGroup; radioGroupForm: FormGroup;
isLoading = true; isLoading = true;
@ -48,13 +48,13 @@ export class PoolRankingComponent implements OnInit {
ngOnInit(): void { ngOnInit(): void {
if (this.widget) { if (this.widget) {
this.poolsWindowPreference = '1w'; this.miningWindowPreference = '1w';
} else { } else {
this.seoService.setTitle($localize`:@@mining.mining-pools:Mining Pools`); this.seoService.setTitle($localize`:@@mining.mining-pools:Mining Pools`);
this.poolsWindowPreference = this.storageService.getValue('poolsWindowPreference') ? this.storageService.getValue('poolsWindowPreference') : '1w'; this.miningWindowPreference = this.miningService.getDefaultTimespan('24h');
} }
this.radioGroupForm = this.formBuilder.group({ dateSpan: this.poolsWindowPreference }); this.radioGroupForm = this.formBuilder.group({ dateSpan: this.miningWindowPreference });
this.radioGroupForm.controls.dateSpan.setValue(this.poolsWindowPreference); this.radioGroupForm.controls.dateSpan.setValue(this.miningWindowPreference);
// When... // When...
this.miningStatsObservable$ = combineLatest([ this.miningStatsObservable$ = combineLatest([
@ -67,12 +67,12 @@ export class PoolRankingComponent implements OnInit {
// ...or we change the timespan // ...or we change the timespan
this.radioGroupForm.get('dateSpan').valueChanges this.radioGroupForm.get('dateSpan').valueChanges
.pipe( .pipe(
startWith(this.poolsWindowPreference), // (trigger when the page loads) startWith(this.miningWindowPreference), // (trigger when the page loads)
tap((value) => { tap((value) => {
if (!this.widget) { if (!this.widget) {
this.storageService.setValue('poolsWindowPreference', value); this.storageService.setValue('miningWindowPreference', value);
} }
this.poolsWindowPreference = value; this.miningWindowPreference = value;
}) })
) )
]) ])
@ -80,7 +80,7 @@ export class PoolRankingComponent implements OnInit {
.pipe( .pipe(
switchMap(() => { switchMap(() => {
this.isLoading = true; this.isLoading = true;
return this.miningService.getMiningStats(this.poolsWindowPreference) return this.miningService.getMiningStats(this.miningWindowPreference)
.pipe( .pipe(
catchError((e) => of(this.getEmptyMiningStat())) catchError((e) => of(this.getEmptyMiningStat()))
); );
@ -150,7 +150,7 @@ export class PoolRankingComponent implements OnInit {
}, },
borderColor: '#000', borderColor: '#000',
formatter: () => { formatter: () => {
if (this.poolsWindowPreference === '24h') { if (this.miningWindowPreference === '24h') {
return `<b style="color: white">${pool.name} (${pool.share}%)</b><br>` + return `<b style="color: white">${pool.name} (${pool.share}%)</b><br>` +
pool.lastEstimatedHashrate.toString() + ' PH/s' + pool.lastEstimatedHashrate.toString() + ' PH/s' +
`<br>` + pool.blockCount.toString() + ` blocks`; `<br>` + pool.blockCount.toString() + ` blocks`;
@ -186,7 +186,7 @@ export class PoolRankingComponent implements OnInit {
}, },
borderColor: '#000', borderColor: '#000',
formatter: () => { formatter: () => {
if (this.poolsWindowPreference === '24h') { if (this.miningWindowPreference === '24h') {
return `<b style="color: white">${'Other'} (${totalShareOther.toFixed(2)}%)</b><br>` + return `<b style="color: white">${'Other'} (${totalShareOther.toFixed(2)}%)</b><br>` +
totalEstimatedHashrateOther.toString() + ' PH/s' + totalEstimatedHashrateOther.toString() + ' PH/s' +
`<br>` + totalBlockOther.toString() + ` blocks`; `<br>` + totalBlockOther.toString() + ` blocks`;

View File

@ -4,6 +4,7 @@ import { map } from 'rxjs/operators';
import { PoolsStats, SinglePoolStats } from '../interfaces/node-api.interface'; import { PoolsStats, SinglePoolStats } from '../interfaces/node-api.interface';
import { ApiService } from '../services/api.service'; import { ApiService } from '../services/api.service';
import { StateService } from './state.service'; import { StateService } from './state.service';
import { StorageService } from './storage.service';
export interface MiningUnits { export interface MiningUnits {
hashrateDivider: number; hashrateDivider: number;
@ -28,8 +29,12 @@ export class MiningService {
constructor( constructor(
private stateService: StateService, private stateService: StateService,
private apiService: ApiService, private apiService: ApiService,
private storageService: StorageService,
) { } ) { }
/**
* Generate pool ranking stats
*/
public getMiningStats(interval: string): Observable<MiningStats> { public getMiningStats(interval: string): Observable<MiningStats> {
return this.apiService.listPools$(interval).pipe( return this.apiService.listPools$(interval).pipe(
map(pools => this.generateMiningStats(pools)) map(pools => this.generateMiningStats(pools))
@ -63,6 +68,20 @@ export class MiningService {
}; };
} }
/**
* Get the default selection timespan, cap with `min`
*/
public getDefaultTimespan(min: string): string {
const timespans = [
'24h', '3d', '1w', '1m', '3m', '6m', '1y', '2y', '3y', 'all'
];
const preference = this.storageService.getValue('miningWindowPreference') ?? '1w';
if (timespans.indexOf(preference) < timespans.indexOf(min)) {
return min;
}
return preference;
}
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;

View File

@ -7,21 +7,21 @@ import { Router, ActivatedRoute } from '@angular/router';
export class StorageService { export class StorageService {
constructor(private router: Router, private route: ActivatedRoute) { constructor(private router: Router, private route: ActivatedRoute) {
this.setDefaultValueIfNeeded('graphWindowPreference', '2h'); this.setDefaultValueIfNeeded('graphWindowPreference', '2h');
this.setDefaultValueIfNeeded('poolsWindowPreference', '1w'); this.setDefaultValueIfNeeded('miningWindowPreference', '1w');
} }
setDefaultValueIfNeeded(key: string, defaultValue: string) { setDefaultValueIfNeeded(key: string, defaultValue: string) {
let graphWindowPreference: string = this.getValue(key); const graphWindowPreference: string = this.getValue(key);
if (graphWindowPreference === null) { // First visit to mempool.space if (graphWindowPreference === null) { // First visit to mempool.space
if (this.router.url.includes('graphs') && key === 'graphWindowPreference' || if (this.router.url.includes('graphs') && key === 'graphWindowPreference' ||
this.router.url.includes('pools') && key === 'poolsWindowPreference' this.router.url.includes('pools') && key === 'miningWindowPreference'
) { ) {
this.setValue(key, this.route.snapshot.fragment ? this.route.snapshot.fragment : defaultValue); this.setValue(key, this.route.snapshot.fragment ? this.route.snapshot.fragment : defaultValue);
} else { } else {
this.setValue(key, defaultValue); this.setValue(key, defaultValue);
} }
} else if (this.router.url.includes('graphs') && key === 'graphWindowPreference' || } else if (this.router.url.includes('graphs') && key === 'graphWindowPreference' ||
this.router.url.includes('pools') && key === 'poolsWindowPreference' this.router.url.includes('pools') && key === 'miningWindowPreference'
) { ) {
// Visit a different graphs#fragment from last visit // Visit a different graphs#fragment from last visit
if (this.route.snapshot.fragment !== null && graphWindowPreference !== this.route.snapshot.fragment) { if (this.route.snapshot.fragment !== null && graphWindowPreference !== this.route.snapshot.fragment) {

View File

@ -38,6 +38,7 @@ do for url in / \
'/api/v1/mining/reward-stats/144' \ '/api/v1/mining/reward-stats/144' \
'/api/v1/mining/blocks-extras' \ '/api/v1/mining/blocks-extras' \
'/api/v1/mining/blocks/fees/24h' \ '/api/v1/mining/blocks/fees/24h' \
'/api/v1/mining/blocks/fees/3d' \
'/api/v1/mining/blocks/fees/1w' \ '/api/v1/mining/blocks/fees/1w' \
'/api/v1/mining/blocks/fees/1m' \ '/api/v1/mining/blocks/fees/1m' \
'/api/v1/mining/blocks/fees/3m' \ '/api/v1/mining/blocks/fees/3m' \