Improve mining graphs timespan selection UX
This commit is contained in:
		
							parent
							
								
									aec8502ee9
								
							
						
					
					
						commit
						61df98ef94
					
				@ -27,6 +27,7 @@ class Mining {
 | 
			
		||||
      case '3m': timeRange = 7200; break; // 2h
 | 
			
		||||
      case '1m': timeRange = 1800; break; // 30min
 | 
			
		||||
      case '1w': timeRange = 300; break; // 5min
 | 
			
		||||
      case '3d': timeRange = 1; break;
 | 
			
		||||
      case '24h': timeRange = 1; break;
 | 
			
		||||
      default: timeRange = 86400; break; // 24h
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
@ -1,41 +1,43 @@
 | 
			
		||||
<div [class]="widget === false ? 'full-container' : ''">
 | 
			
		||||
 | 
			
		||||
  <div class="card-header mb-0 mb-md-4" [style]="widget ? 'display:none' : ''">
 | 
			
		||||
<div class="full-container">
 | 
			
		||||
  <div class="card-header mb-0 mb-md-4">
 | 
			
		||||
    <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">
 | 
			
		||||
        <label ngbButtonLabel class="btn-primary btn-sm" *ngIf="hashrates.availableTimespanDay >= 1">
 | 
			
		||||
          <input ngbButton type="radio" [value]="'24h'"> 24h
 | 
			
		||||
        <label ngbButtonLabel class="btn-primary btn-sm" *ngIf="stats.availableTimespanDay >= 1">
 | 
			
		||||
          <input ngbButton type="radio" [value]="'24h'" fragment="24h"> 24h
 | 
			
		||||
        </label>
 | 
			
		||||
        <label ngbButtonLabel class="btn-primary btn-sm" *ngIf="hashrates.availableTimespanDay >= 7">
 | 
			
		||||
          <input ngbButton type="radio" [value]="'1w'"> 1W
 | 
			
		||||
        <label ngbButtonLabel class="btn-primary btn-sm" *ngIf="stats.availableTimespanDay >= 3">
 | 
			
		||||
          <input ngbButton type="radio" [value]="'3d'" fragment="3d"> 3D
 | 
			
		||||
        </label>
 | 
			
		||||
        <label ngbButtonLabel class="btn-primary btn-sm" *ngIf="hashrates.availableTimespanDay >= 30">
 | 
			
		||||
          <input ngbButton type="radio" [value]="'1m'"> 1M
 | 
			
		||||
        <label ngbButtonLabel class="btn-primary btn-sm" *ngIf="stats.availableTimespanDay >= 7">
 | 
			
		||||
          <input ngbButton type="radio" [value]="'1w'" fragment="1w"> 1W
 | 
			
		||||
        </label>
 | 
			
		||||
        <label ngbButtonLabel class="btn-primary btn-sm" *ngIf="hashrates.availableTimespanDay >= 90">
 | 
			
		||||
          <input ngbButton type="radio" [value]="'3m'"> 3M
 | 
			
		||||
        <label ngbButtonLabel class="btn-primary btn-sm" *ngIf="stats.availableTimespanDay >= 30">
 | 
			
		||||
          <input ngbButton type="radio" [value]="'1m'" fragment="1m"> 1M
 | 
			
		||||
        </label>
 | 
			
		||||
        <label ngbButtonLabel class="btn-primary btn-sm" *ngIf="hashrates.availableTimespanDay >= 180">
 | 
			
		||||
          <input ngbButton type="radio" [value]="'6m'"> 6M
 | 
			
		||||
        <label ngbButtonLabel class="btn-primary btn-sm" *ngIf="stats.availableTimespanDay >= 90">
 | 
			
		||||
          <input ngbButton type="radio" [value]="'3m'" fragment="3m"> 3M
 | 
			
		||||
        </label>
 | 
			
		||||
        <label ngbButtonLabel class="btn-primary btn-sm" *ngIf="hashrates.availableTimespanDay >= 365">
 | 
			
		||||
          <input ngbButton type="radio" [value]="'1y'"> 1Y
 | 
			
		||||
        <label ngbButtonLabel class="btn-primary btn-sm" *ngIf="stats.availableTimespanDay >= 180">
 | 
			
		||||
          <input ngbButton type="radio" [value]="'6m'" fragment="6m"> 6M
 | 
			
		||||
        </label>
 | 
			
		||||
        <label ngbButtonLabel class="btn-primary btn-sm" *ngIf="hashrates.availableTimespanDay >= 730">
 | 
			
		||||
          <input ngbButton type="radio" [value]="'2y'"> 2Y
 | 
			
		||||
        <label ngbButtonLabel class="btn-primary btn-sm" *ngIf="stats.availableTimespanDay >= 365">
 | 
			
		||||
          <input ngbButton type="radio" [value]="'1y'" fragment="1y"> 1Y
 | 
			
		||||
        </label>
 | 
			
		||||
        <label ngbButtonLabel class="btn-primary btn-sm" *ngIf="hashrates.availableTimespanDay >= 1095">
 | 
			
		||||
          <input ngbButton type="radio" [value]="'3y'"> 3Y
 | 
			
		||||
        <label ngbButtonLabel class="btn-primary btn-sm" *ngIf="stats.availableTimespanDay >= 730">
 | 
			
		||||
          <input ngbButton type="radio" [value]="'2y'" fragment="2y"> 2Y
 | 
			
		||||
        </label>
 | 
			
		||||
        <label ngbButtonLabel class="btn-primary btn-sm">
 | 
			
		||||
          <input ngbButton type="radio" [value]="'all'"> ALL
 | 
			
		||||
        <label ngbButtonLabel class="btn-primary btn-sm" *ngIf="stats.availableTimespanDay >= 1095">
 | 
			
		||||
          <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>
 | 
			
		||||
      </div>
 | 
			
		||||
    </form>
 | 
			
		||||
  </div>
 | 
			
		||||
 | 
			
		||||
  <div [class]="!widget ? 'chart' : 'chart-widget'" echarts [initOpts]="chartInitOptions" [options]="chartOptions">
 | 
			
		||||
  <div class="chart" echarts [initOpts]="chartInitOptions" [options]="chartOptions">
 | 
			
		||||
  </div>
 | 
			
		||||
  <div class="text-center loadingGraphs" *ngIf="isLoading">
 | 
			
		||||
    <div class="spinner-border text-light"></div>
 | 
			
		||||
 | 
			
		||||
@ -7,6 +7,8 @@ import { SeoService } from 'src/app/services/seo.service';
 | 
			
		||||
import { formatNumber } from '@angular/common';
 | 
			
		||||
import { FormBuilder, FormGroup } from '@angular/forms';
 | 
			
		||||
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({
 | 
			
		||||
  selector: 'app-block-fees-graph',
 | 
			
		||||
@ -24,10 +26,10 @@ import { formatterXAxisLabel } from 'src/app/shared/graphs.utils';
 | 
			
		||||
})
 | 
			
		||||
export class BlockFeesGraphComponent implements OnInit {
 | 
			
		||||
  @Input() tableOnly = false;
 | 
			
		||||
  @Input() widget = false;
 | 
			
		||||
  @Input() right: number | string = 45;
 | 
			
		||||
  @Input() left: number | string = 75;
 | 
			
		||||
 | 
			
		||||
  miningWindowPreference: string;
 | 
			
		||||
  radioGroupForm: FormGroup;
 | 
			
		||||
 | 
			
		||||
  chartOptions: EChartsOption = {};
 | 
			
		||||
@ -44,21 +46,25 @@ export class BlockFeesGraphComponent implements OnInit {
 | 
			
		||||
    @Inject(LOCALE_ID) public locale: string,
 | 
			
		||||
    private seoService: SeoService,
 | 
			
		||||
    private apiService: ApiService,
 | 
			
		||||
    private formBuilder: FormBuilder
 | 
			
		||||
    private formBuilder: FormBuilder,
 | 
			
		||||
    private storageService: StorageService,
 | 
			
		||||
    private miningService: MiningService
 | 
			
		||||
  ) {
 | 
			
		||||
    this.radioGroupForm = this.formBuilder.group({ dateSpan: '1y' });
 | 
			
		||||
    this.radioGroupForm.controls.dateSpan.setValue('1y');
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  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
 | 
			
		||||
      .pipe(
 | 
			
		||||
        startWith('1y'),
 | 
			
		||||
        startWith(this.miningWindowPreference),
 | 
			
		||||
        switchMap((timespan) => {
 | 
			
		||||
          this.storageService.setValue('miningWindowPreference', timespan);
 | 
			
		||||
          this.timespan = timespan;
 | 
			
		||||
          this.isLoading = true;
 | 
			
		||||
          return this.apiService.getHistoricalBlockFees$(timespan)
 | 
			
		||||
@ -103,7 +109,7 @@ export class BlockFeesGraphComponent implements OnInit {
 | 
			
		||||
        left: this.left,
 | 
			
		||||
      },
 | 
			
		||||
      tooltip: {
 | 
			
		||||
        show: !this.isMobile() || !this.widget,
 | 
			
		||||
        show: !this.isMobile(),
 | 
			
		||||
        trigger: 'axis',
 | 
			
		||||
        axisPointer: {
 | 
			
		||||
          type: 'line'
 | 
			
		||||
@ -161,7 +167,7 @@ export class BlockFeesGraphComponent implements OnInit {
 | 
			
		||||
          },
 | 
			
		||||
        },
 | 
			
		||||
      ],
 | 
			
		||||
      dataZoom: this.widget ? null : [{
 | 
			
		||||
      dataZoom: [{
 | 
			
		||||
        type: 'inside',
 | 
			
		||||
        realtime: true,
 | 
			
		||||
        zoomLock: true,
 | 
			
		||||
 | 
			
		||||
@ -20,25 +20,28 @@
 | 
			
		||||
 | 
			
		||||
  <div class="card-header mb-0 mb-md-4" [style]="widget ? 'display:none' : ''">
 | 
			
		||||
    <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">
 | 
			
		||||
        <label ngbButtonLabel class="btn-primary btn-sm" *ngIf="hashrates.availableTimespanDay >= 90">
 | 
			
		||||
          <input ngbButton type="radio" [value]="'3m'"> 3M
 | 
			
		||||
        <label ngbButtonLabel class="btn-primary btn-sm" *ngIf="stats.availableTimespanDay >= 30">
 | 
			
		||||
          <input ngbButton type="radio" [value]="'1m'" fragment="1m"> 1M
 | 
			
		||||
        </label>
 | 
			
		||||
        <label ngbButtonLabel class="btn-primary btn-sm" *ngIf="hashrates.availableTimespanDay >= 180">
 | 
			
		||||
          <input ngbButton type="radio" [value]="'6m'"> 6M
 | 
			
		||||
        <label ngbButtonLabel class="btn-primary btn-sm" *ngIf="stats.availableTimespanDay >= 90">
 | 
			
		||||
          <input ngbButton type="radio" [value]="'3m'" fragment="3m"> 3M
 | 
			
		||||
        </label>
 | 
			
		||||
        <label ngbButtonLabel class="btn-primary btn-sm" *ngIf="hashrates.availableTimespanDay >= 365">
 | 
			
		||||
          <input ngbButton type="radio" [value]="'1y'"> 1Y
 | 
			
		||||
        <label ngbButtonLabel class="btn-primary btn-sm" *ngIf="stats.availableTimespanDay >= 180">
 | 
			
		||||
          <input ngbButton type="radio" [value]="'6m'" fragment="6m"> 6M
 | 
			
		||||
        </label>
 | 
			
		||||
        <label ngbButtonLabel class="btn-primary btn-sm" *ngIf="hashrates.availableTimespanDay >= 730">
 | 
			
		||||
          <input ngbButton type="radio" [value]="'2y'"> 2Y
 | 
			
		||||
        <label ngbButtonLabel class="btn-primary btn-sm" *ngIf="stats.availableTimespanDay >= 365">
 | 
			
		||||
          <input ngbButton type="radio" [value]="'1y'" fragment="1y"> 1Y
 | 
			
		||||
        </label>
 | 
			
		||||
        <label ngbButtonLabel class="btn-primary btn-sm" *ngIf="hashrates.availableTimespanDay >= 1095">
 | 
			
		||||
          <input ngbButton type="radio" [value]="'3y'"> 3Y
 | 
			
		||||
        <label ngbButtonLabel class="btn-primary btn-sm" *ngIf="stats.availableTimespanDay >= 730">
 | 
			
		||||
          <input ngbButton type="radio" [value]="'2y'" fragment="2y"> 2Y
 | 
			
		||||
        </label>
 | 
			
		||||
        <label ngbButtonLabel class="btn-primary btn-sm">
 | 
			
		||||
          <input ngbButton type="radio" [value]="'all'"> ALL
 | 
			
		||||
        <label ngbButtonLabel class="btn-primary btn-sm" *ngIf="stats.availableTimespanDay >= 1095">
 | 
			
		||||
          <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>
 | 
			
		||||
      </div>
 | 
			
		||||
    </form>
 | 
			
		||||
 | 
			
		||||
@ -132,4 +132,4 @@
 | 
			
		||||
  display: block;
 | 
			
		||||
  max-width: 80px;
 | 
			
		||||
  margin: 15px auto 3px;
 | 
			
		||||
}
 | 
			
		||||
}
 | 
			
		||||
@ -7,6 +7,8 @@ import { SeoService } from 'src/app/services/seo.service';
 | 
			
		||||
import { formatNumber } from '@angular/common';
 | 
			
		||||
import { FormBuilder, FormGroup } from '@angular/forms';
 | 
			
		||||
import { selectPowerOfTen } from 'src/app/bitcoin.utils';
 | 
			
		||||
import { StorageService } from 'src/app/services/storage.service';
 | 
			
		||||
import { MiningService } from 'src/app/services/mining.service';
 | 
			
		||||
 | 
			
		||||
@Component({
 | 
			
		||||
  selector: 'app-hashrate-chart',
 | 
			
		||||
@ -28,6 +30,7 @@ export class HashrateChartComponent implements OnInit {
 | 
			
		||||
  @Input() right: number | string = 45;
 | 
			
		||||
  @Input() left: number | string = 75;
 | 
			
		||||
 | 
			
		||||
  miningWindowPreference: string;
 | 
			
		||||
  radioGroupForm: FormGroup;
 | 
			
		||||
 | 
			
		||||
  chartOptions: EChartsOption = {};
 | 
			
		||||
@ -47,20 +50,32 @@ export class HashrateChartComponent implements OnInit {
 | 
			
		||||
    private apiService: ApiService,
 | 
			
		||||
    private formBuilder: FormBuilder,
 | 
			
		||||
    private cd: ChangeDetectorRef,
 | 
			
		||||
    private storageService: StorageService,
 | 
			
		||||
    private miningService: MiningService
 | 
			
		||||
  ) {
 | 
			
		||||
    this.radioGroupForm = this.formBuilder.group({ dateSpan: '1y' });
 | 
			
		||||
    this.radioGroupForm.controls.dateSpan.setValue('1y');
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  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.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
 | 
			
		||||
      .pipe(
 | 
			
		||||
        startWith('1y'),
 | 
			
		||||
        startWith(this.miningWindowPreference),
 | 
			
		||||
        switchMap((timespan) => {
 | 
			
		||||
          if (!this.widget && !firstRun) {
 | 
			
		||||
            this.storageService.setValue('miningWindowPreference', timespan);
 | 
			
		||||
          }
 | 
			
		||||
          firstRun = false;
 | 
			
		||||
          this.miningWindowPreference = timespan;
 | 
			
		||||
          this.isLoading = true;
 | 
			
		||||
          return this.apiService.getHistoricalHashrate$(timespan)
 | 
			
		||||
            .pipe(
 | 
			
		||||
 | 
			
		||||
@ -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' : ''">
 | 
			
		||||
    <span *ngIf="!widget" i18n="mining.pools-dominance">Mining pools dominance</span>
 | 
			
		||||
    <form [formGroup]="radioGroupForm" class="formRadioGroup" *ngIf="(hashrateObservable$ | async) as hashrates">
 | 
			
		||||
  <div class="card-header  mb-0 mb-md-4">
 | 
			
		||||
    <span i18n="mining.pools-dominance">Mining pools dominance</span>
 | 
			
		||||
    <form [formGroup]="radioGroupForm" class="formRadioGroup" *ngIf="(hashrateObservable$ | async) as stats">
 | 
			
		||||
      <div class="btn-group btn-group-toggle" ngbRadioGroup name="radioBasic" formControlName="dateSpan">
 | 
			
		||||
        <label ngbButtonLabel class="btn-primary btn-sm" *ngIf="hashrates.availableTimespanDay >= 90">
 | 
			
		||||
          <input ngbButton type="radio" [value]="'3m'"> 3M
 | 
			
		||||
        <label ngbButtonLabel class="btn-primary btn-sm" *ngIf="stats.availableTimespanDay >= 30">
 | 
			
		||||
          <input ngbButton type="radio" [value]="'1m'" fragment="1m"> 1M
 | 
			
		||||
        </label>
 | 
			
		||||
        <label ngbButtonLabel class="btn-primary btn-sm" *ngIf="hashrates.availableTimespanDay >= 180">
 | 
			
		||||
          <input ngbButton type="radio" [value]="'6m'"> 6M
 | 
			
		||||
        <label ngbButtonLabel class="btn-primary btn-sm" *ngIf="stats.availableTimespanDay >= 90">
 | 
			
		||||
          <input ngbButton type="radio" [value]="'3m'" fragment="3m"> 3M
 | 
			
		||||
        </label>
 | 
			
		||||
        <label ngbButtonLabel class="btn-primary btn-sm" *ngIf="hashrates.availableTimespanDay >= 365">
 | 
			
		||||
          <input ngbButton type="radio" [value]="'1y'"> 1Y
 | 
			
		||||
        <label ngbButtonLabel class="btn-primary btn-sm" *ngIf="stats.availableTimespanDay >= 180">
 | 
			
		||||
          <input ngbButton type="radio" [value]="'6m'" fragment="6m"> 6M
 | 
			
		||||
        </label>
 | 
			
		||||
        <label ngbButtonLabel class="btn-primary btn-sm" *ngIf="hashrates.availableTimespanDay >= 730">
 | 
			
		||||
          <input ngbButton type="radio" [value]="'2y'"> 2Y
 | 
			
		||||
        <label ngbButtonLabel class="btn-primary btn-sm" *ngIf="stats.availableTimespanDay >= 365">
 | 
			
		||||
          <input ngbButton type="radio" [value]="'1y'" fragment="1y"> 1Y
 | 
			
		||||
        </label>
 | 
			
		||||
        <label ngbButtonLabel class="btn-primary btn-sm" *ngIf="hashrates.availableTimespanDay >= 1095">
 | 
			
		||||
          <input ngbButton type="radio" [value]="'3y'"> 3Y
 | 
			
		||||
        <label ngbButtonLabel class="btn-primary btn-sm" *ngIf="stats.availableTimespanDay >= 730">
 | 
			
		||||
          <input ngbButton type="radio" [value]="'2y'" fragment="2y"> 2Y
 | 
			
		||||
        </label>
 | 
			
		||||
        <label ngbButtonLabel class="btn-primary btn-sm">
 | 
			
		||||
          <input ngbButton type="radio" [value]="'all'"> ALL
 | 
			
		||||
        <label ngbButtonLabel class="btn-primary btn-sm" *ngIf="stats.availableTimespanDay >= 1095">
 | 
			
		||||
          <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>
 | 
			
		||||
      </div>
 | 
			
		||||
    </form>
 | 
			
		||||
  </div>
 | 
			
		||||
 | 
			
		||||
  <div [class]="!widget ? 'chart' : 'chart-widget'"
 | 
			
		||||
  <div class="chart"
 | 
			
		||||
    echarts [initOpts]="chartInitOptions" [options]="chartOptions"></div>
 | 
			
		||||
  <div class="text-center loadingGraphs" *ngIf="isLoading">
 | 
			
		||||
    <div class="spinner-border text-light"></div>
 | 
			
		||||
 | 
			
		||||
@ -6,6 +6,8 @@ import { ApiService } from 'src/app/services/api.service';
 | 
			
		||||
import { SeoService } from 'src/app/services/seo.service';
 | 
			
		||||
import { FormBuilder, FormGroup } from '@angular/forms';
 | 
			
		||||
import { poolsColor } from 'src/app/app.constants';
 | 
			
		||||
import { StorageService } from 'src/app/services/storage.service';
 | 
			
		||||
import { MiningService } from 'src/app/services/mining.service';
 | 
			
		||||
 | 
			
		||||
@Component({
 | 
			
		||||
  selector: 'app-hashrate-chart-pools',
 | 
			
		||||
@ -22,10 +24,10 @@ import { poolsColor } from 'src/app/app.constants';
 | 
			
		||||
  changeDetection: ChangeDetectionStrategy.OnPush,
 | 
			
		||||
})
 | 
			
		||||
export class HashrateChartPoolsComponent implements OnInit {
 | 
			
		||||
  @Input() widget = false;
 | 
			
		||||
  @Input() right: number | string = 45;
 | 
			
		||||
  @Input() left: number | string = 25;
 | 
			
		||||
 | 
			
		||||
  miningWindowPreference: string;
 | 
			
		||||
  radioGroupForm: FormGroup;
 | 
			
		||||
 | 
			
		||||
  chartOptions: EChartsOption = {};
 | 
			
		||||
@ -44,20 +46,29 @@ export class HashrateChartPoolsComponent implements OnInit {
 | 
			
		||||
    private apiService: ApiService,
 | 
			
		||||
    private formBuilder: FormBuilder,
 | 
			
		||||
    private cd: ChangeDetectorRef,
 | 
			
		||||
    private storageService: StorageService,
 | 
			
		||||
    private miningService: MiningService
 | 
			
		||||
  ) {
 | 
			
		||||
    this.radioGroupForm = this.formBuilder.group({ dateSpan: '1y' });
 | 
			
		||||
    this.radioGroupForm.controls.dateSpan.setValue('1y');
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  ngOnInit(): void {
 | 
			
		||||
    if (!this.widget) {
 | 
			
		||||
      this.seoService.setTitle($localize`:@@mining.pools-historical-dominance:Pools Historical Dominance`);
 | 
			
		||||
    }
 | 
			
		||||
    let firstRun = true;
 | 
			
		||||
 | 
			
		||||
    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
 | 
			
		||||
      .pipe(
 | 
			
		||||
        startWith('1y'),
 | 
			
		||||
        startWith(this.miningWindowPreference),
 | 
			
		||||
        switchMap((timespan) => {
 | 
			
		||||
          if (!firstRun) {
 | 
			
		||||
            this.storageService.setValue('miningWindowPreference', timespan);
 | 
			
		||||
          }
 | 
			
		||||
          firstRun = false;
 | 
			
		||||
          this.isLoading = true;
 | 
			
		||||
          return this.apiService.getHistoricalPoolsHashrate$(timespan)
 | 
			
		||||
            .pipe(
 | 
			
		||||
@ -157,11 +168,11 @@ export class HashrateChartPoolsComponent implements OnInit {
 | 
			
		||||
      grid: {
 | 
			
		||||
        right: this.right,
 | 
			
		||||
        left: this.left,
 | 
			
		||||
        bottom: this.widget ? 30 : 70,
 | 
			
		||||
        top: this.widget || this.isMobile() ? 10 : 50,
 | 
			
		||||
        bottom: 70,
 | 
			
		||||
        top: this.isMobile() ? 10 : 50,
 | 
			
		||||
      },
 | 
			
		||||
      tooltip: {
 | 
			
		||||
        show: !this.isMobile() || !this.widget,
 | 
			
		||||
        show: !this.isMobile(),
 | 
			
		||||
        trigger: 'axis',
 | 
			
		||||
        axisPointer: {
 | 
			
		||||
          type: 'line'
 | 
			
		||||
@ -188,9 +199,9 @@ export class HashrateChartPoolsComponent implements OnInit {
 | 
			
		||||
      },
 | 
			
		||||
      xAxis: data.series.length === 0 ? undefined : {
 | 
			
		||||
        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
 | 
			
		||||
      },
 | 
			
		||||
      yAxis: data.series.length === 0 ? undefined : {
 | 
			
		||||
@ -207,7 +218,7 @@ export class HashrateChartPoolsComponent implements OnInit {
 | 
			
		||||
        min: 0,
 | 
			
		||||
      },
 | 
			
		||||
      series: data.series,
 | 
			
		||||
      dataZoom: this.widget ? null : [{
 | 
			
		||||
      dataZoom: [{
 | 
			
		||||
        type: 'inside',
 | 
			
		||||
        realtime: true,
 | 
			
		||||
        zoomLock: true,
 | 
			
		||||
 | 
			
		||||
@ -55,7 +55,7 @@
 | 
			
		||||
        <label ngbButtonLabel class="btn-primary btn-sm" *ngIf="miningStats.availableTimespanDay >= 1095">
 | 
			
		||||
          <input ngbButton type="radio" [value]="'3y'" fragment="3y"> 3Y
 | 
			
		||||
        </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
 | 
			
		||||
        </label>
 | 
			
		||||
      </div>
 | 
			
		||||
@ -79,7 +79,7 @@
 | 
			
		||||
          <th class="d-none d-md-block" i18n="mining.rank">Rank</th>
 | 
			
		||||
          <th class=""></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="d-none d-md-block" i18n="mining.empty-blocks">Empty Blocks</th>
 | 
			
		||||
        </tr>
 | 
			
		||||
@ -90,7 +90,7 @@
 | 
			
		||||
          <td class="text-right"><img width="25" height="25" src="{{ pool.logo }}"
 | 
			
		||||
              onError="this.src = './resources/mining-pools/default.svg'"></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>
 | 
			
		||||
          <td class="">{{ pool['blockText'] }}</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="text-right"></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>
 | 
			
		||||
          <td class=""><b>{{ miningStats.blockCount }}</b></td>
 | 
			
		||||
          <td class="d-none d-md-block"><b>{{ miningStats.totalEmptyBlock }} ({{ miningStats.totalEmptyBlockRatio
 | 
			
		||||
 | 
			
		||||
@ -19,9 +19,9 @@ import { RelativeUrlPipe } from 'src/app/shared/pipes/relative-url/relative-url.
 | 
			
		||||
  changeDetection: ChangeDetectionStrategy.OnPush,
 | 
			
		||||
})
 | 
			
		||||
export class PoolRankingComponent implements OnInit {
 | 
			
		||||
  @Input() widget: boolean = false;
 | 
			
		||||
  @Input() widget = false;
 | 
			
		||||
 | 
			
		||||
  poolsWindowPreference: string;
 | 
			
		||||
  miningWindowPreference: string;
 | 
			
		||||
  radioGroupForm: FormGroup;
 | 
			
		||||
 | 
			
		||||
  isLoading = true;
 | 
			
		||||
@ -48,13 +48,13 @@ export class PoolRankingComponent implements OnInit {
 | 
			
		||||
 | 
			
		||||
  ngOnInit(): void {
 | 
			
		||||
    if (this.widget) {
 | 
			
		||||
      this.poolsWindowPreference = '1w';
 | 
			
		||||
      this.miningWindowPreference = '1w';
 | 
			
		||||
    } else {
 | 
			
		||||
      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.controls.dateSpan.setValue(this.poolsWindowPreference);
 | 
			
		||||
    this.radioGroupForm = this.formBuilder.group({ dateSpan: this.miningWindowPreference });
 | 
			
		||||
    this.radioGroupForm.controls.dateSpan.setValue(this.miningWindowPreference);
 | 
			
		||||
 | 
			
		||||
    // When...
 | 
			
		||||
    this.miningStatsObservable$ = combineLatest([
 | 
			
		||||
@ -67,12 +67,12 @@ export class PoolRankingComponent implements OnInit {
 | 
			
		||||
      // ...or we change the timespan
 | 
			
		||||
      this.radioGroupForm.get('dateSpan').valueChanges
 | 
			
		||||
        .pipe(
 | 
			
		||||
          startWith(this.poolsWindowPreference), // (trigger when the page loads)
 | 
			
		||||
          startWith(this.miningWindowPreference), // (trigger when the page loads)
 | 
			
		||||
          tap((value) => {
 | 
			
		||||
            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(
 | 
			
		||||
        switchMap(() => {
 | 
			
		||||
          this.isLoading = true;
 | 
			
		||||
          return this.miningService.getMiningStats(this.poolsWindowPreference)
 | 
			
		||||
          return this.miningService.getMiningStats(this.miningWindowPreference)
 | 
			
		||||
            .pipe(
 | 
			
		||||
              catchError((e) => of(this.getEmptyMiningStat()))
 | 
			
		||||
            );
 | 
			
		||||
@ -150,7 +150,7 @@ export class PoolRankingComponent implements OnInit {
 | 
			
		||||
          },
 | 
			
		||||
          borderColor: '#000',
 | 
			
		||||
          formatter: () => {
 | 
			
		||||
            if (this.poolsWindowPreference === '24h') {
 | 
			
		||||
            if (this.miningWindowPreference === '24h') {
 | 
			
		||||
              return `<b style="color: white">${pool.name} (${pool.share}%)</b><br>` +
 | 
			
		||||
                pool.lastEstimatedHashrate.toString() + ' PH/s' +
 | 
			
		||||
                `<br>` + pool.blockCount.toString() + ` blocks`;
 | 
			
		||||
@ -186,7 +186,7 @@ export class PoolRankingComponent implements OnInit {
 | 
			
		||||
        },
 | 
			
		||||
        borderColor: '#000',
 | 
			
		||||
        formatter: () => {
 | 
			
		||||
          if (this.poolsWindowPreference === '24h') {
 | 
			
		||||
          if (this.miningWindowPreference === '24h') {
 | 
			
		||||
            return `<b style="color: white">${'Other'} (${totalShareOther.toFixed(2)}%)</b><br>` +
 | 
			
		||||
              totalEstimatedHashrateOther.toString() + ' PH/s' +
 | 
			
		||||
              `<br>` + totalBlockOther.toString() + ` blocks`;
 | 
			
		||||
 | 
			
		||||
@ -4,6 +4,7 @@ import { map } from 'rxjs/operators';
 | 
			
		||||
import { PoolsStats, SinglePoolStats } from '../interfaces/node-api.interface';
 | 
			
		||||
import { ApiService } from '../services/api.service';
 | 
			
		||||
import { StateService } from './state.service';
 | 
			
		||||
import { StorageService } from './storage.service';
 | 
			
		||||
 | 
			
		||||
export interface MiningUnits {
 | 
			
		||||
  hashrateDivider: number;
 | 
			
		||||
@ -28,8 +29,12 @@ export class MiningService {
 | 
			
		||||
  constructor(
 | 
			
		||||
    private stateService: StateService,
 | 
			
		||||
    private apiService: ApiService,
 | 
			
		||||
    private storageService: StorageService,
 | 
			
		||||
  ) { }
 | 
			
		||||
 | 
			
		||||
  /**
 | 
			
		||||
   * Generate pool ranking stats
 | 
			
		||||
   */
 | 
			
		||||
  public getMiningStats(interval: string): Observable<MiningStats> {
 | 
			
		||||
    return this.apiService.listPools$(interval).pipe(
 | 
			
		||||
      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 {
 | 
			
		||||
    const miningUnits = this.getMiningUnits();
 | 
			
		||||
    const hashrateDivider = miningUnits.hashrateDivider;
 | 
			
		||||
 | 
			
		||||
@ -7,21 +7,21 @@ import { Router, ActivatedRoute } from '@angular/router';
 | 
			
		||||
export class StorageService {
 | 
			
		||||
  constructor(private router: Router, private route: ActivatedRoute) {
 | 
			
		||||
    this.setDefaultValueIfNeeded('graphWindowPreference', '2h');
 | 
			
		||||
    this.setDefaultValueIfNeeded('poolsWindowPreference', '1w');
 | 
			
		||||
    this.setDefaultValueIfNeeded('miningWindowPreference', '1w');
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  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 (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);
 | 
			
		||||
      } else {
 | 
			
		||||
        this.setValue(key, defaultValue);
 | 
			
		||||
      }
 | 
			
		||||
    } 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
 | 
			
		||||
      if (this.route.snapshot.fragment !== null && graphWindowPreference !== this.route.snapshot.fragment) {
 | 
			
		||||
 | 
			
		||||
@ -38,6 +38,7 @@ do for url in / \
 | 
			
		||||
	'/api/v1/mining/reward-stats/144' \
 | 
			
		||||
	'/api/v1/mining/blocks-extras' \
 | 
			
		||||
	'/api/v1/mining/blocks/fees/24h' \
 | 
			
		||||
	'/api/v1/mining/blocks/fees/3d' \
 | 
			
		||||
	'/api/v1/mining/blocks/fees/1w' \
 | 
			
		||||
	'/api/v1/mining/blocks/fees/1m' \
 | 
			
		||||
	'/api/v1/mining/blocks/fees/3m' \
 | 
			
		||||
 | 
			
		||||
		Loading…
	
	
			
			x
			
			
		
	
		Reference in New Issue
	
	Block a user