mempool/frontend/src/app/components/pool-ranking/pool-ranking.component.ts

324 lines
10 KiB
TypeScript
Raw Normal View History

2022-04-05 20:37:18 +02:00
import { ChangeDetectionStrategy, Component, Input, NgZone, OnInit, HostBinding } from '@angular/core';
2022-11-28 11:55:23 +09:00
import { UntypedFormBuilder, UntypedFormGroup } from '@angular/forms';
import { ActivatedRoute, Router } from '@angular/router';
2023-11-06 18:19:54 +00:00
import { EChartsOption, PieSeriesOption } from '../../graphs/echarts';
import { merge, Observable } from 'rxjs';
2022-06-01 14:07:01 +02:00
import { map, share, startWith, switchMap, tap } from 'rxjs/operators';
2022-09-21 17:23:45 +02:00
import { SeoService } from '../../services/seo.service';
2022-01-21 11:17:36 +09:00
import { StorageService } from '../..//services/storage.service';
import { MiningService, MiningStats } from '../../services/mining.service';
2022-01-21 11:17:36 +09:00
import { StateService } from '../../services/state.service';
2022-09-21 17:23:45 +02:00
import { chartColors, poolsColor } from '../../app.constants';
import { RelativeUrlPipe } from '../../shared/pipes/relative-url/relative-url.pipe';
import { download } from '../../shared/graphs.utils';
import { isMobile } from '../../shared/common.utils';
@Component({
selector: 'app-pool-ranking',
templateUrl: './pool-ranking.component.html',
styleUrls: ['./pool-ranking.component.scss'],
changeDetection: ChangeDetectionStrategy.OnPush,
})
2022-02-16 21:20:28 +09:00
export class PoolRankingComponent implements OnInit {
@Input() height: number = 300;
@Input() widget = false;
miningWindowPreference: string;
2022-11-28 11:55:23 +09:00
radioGroupForm: UntypedFormGroup;
auditAvailable = false;
indexingAvailable = false;
isLoading = true;
chartOptions: EChartsOption = {};
chartInitOptions = {
renderer: 'svg',
};
2022-05-05 16:18:28 +09:00
timespan = '';
chartInstance: any = undefined;
2022-04-05 20:37:18 +02:00
@HostBinding('attr.dir') dir = 'ltr';
2022-01-21 11:17:36 +09:00
miningStatsObservable$: Observable<MiningStats>;
constructor(
2023-11-02 01:29:55 +00:00
public stateService: StateService,
private storageService: StorageService,
2022-11-28 11:55:23 +09:00
private formBuilder: UntypedFormBuilder,
private miningService: MiningService,
private seoService: SeoService,
private router: Router,
private zone: NgZone,
private route: ActivatedRoute,
) {
}
ngOnInit(): void {
if (this.widget) {
this.miningWindowPreference = '1w';
} else {
this.seoService.setTitle($localize`:@@mining.mining-pools:Mining Pools`);
this.seoService.setDescription($localize`:@@meta.description.bitcoin.graphs.pool-ranking:See the top Bitcoin mining pools ranked by number of blocks mined, over your desired timeframe.`);
this.miningWindowPreference = this.miningService.getDefaultTimespan('24h');
}
this.radioGroupForm = this.formBuilder.group({ dateSpan: this.miningWindowPreference });
this.radioGroupForm.controls.dateSpan.setValue(this.miningWindowPreference);
this.indexingAvailable = (this.stateService.env.BASE_MODULE === 'mempool' &&
this.stateService.env.MINING_DASHBOARD === true);
this.auditAvailable = this.indexingAvailable && this.stateService.env.AUDIT;
this.route
.fragment
.subscribe((fragment) => {
if (['24h', '3d', '1w', '1m', '3m', '6m', '1y', '2y', '3y', 'all'].indexOf(fragment) > -1) {
this.radioGroupForm.controls.dateSpan.setValue(fragment, { emitEvent: false });
}
});
this.miningStatsObservable$ = merge(
2022-01-21 11:17:36 +09:00
this.radioGroupForm.get('dateSpan').valueChanges
.pipe(
startWith(this.radioGroupForm.controls.dateSpan.value), // (trigger when the page loads)
2022-01-21 11:17:36 +09:00
tap((value) => {
this.isLoading = true;
2022-05-05 16:18:28 +09:00
this.timespan = value;
if (!this.widget) {
this.storageService.setValue('miningWindowPreference', value);
}
this.miningWindowPreference = value;
2022-06-01 14:07:01 +02:00
}),
switchMap(() => {
return this.miningService.getMiningStats(this.miningWindowPreference);
2022-01-21 11:17:36 +09:00
})
2022-06-01 14:07:01 +02:00
),
this.stateService.chainTip$
2022-06-01 14:07:01 +02:00
.pipe(
switchMap(() => {
return this.miningService.getMiningStats(this.miningWindowPreference);
})
)
)
2022-01-21 11:17:36 +09:00
.pipe(
map(data => {
data['minersLuck'] = (100 * (data.blockCount / 1008)).toFixed(2); // luck 1w
return data;
}),
2022-01-21 11:17:36 +09:00
tap(data => {
this.isLoading = false;
this.prepareChartOptions(data);
}),
share()
2022-01-21 11:17:36 +09:00
);
}
2022-01-21 11:17:36 +09:00
generatePoolsChartSerieData(miningStats) {
let poolShareThreshold = 0.5;
if (isMobile()) {
poolShareThreshold = 2;
} else if (this.widget) {
poolShareThreshold = 1;
}
const data: object[] = [];
let totalShareOther = 0;
let totalBlockOther = 0;
let totalEstimatedHashrateOther = 0;
let edgeDistance: any = '20%';
if (isMobile() && this.widget) {
edgeDistance = 0;
} else if (isMobile() && !this.widget || this.widget) {
edgeDistance = 10;
}
2022-01-21 11:17:36 +09:00
miningStats.pools.forEach((pool) => {
if (parseFloat(pool.share) < poolShareThreshold) {
totalShareOther += parseFloat(pool.share);
totalBlockOther += pool.blockCount;
totalEstimatedHashrateOther += pool.lastEstimatedHashrate;
return;
}
data.push({
itemStyle: {
color: poolsColor[pool.name.replace(/[^a-zA-Z0-9]/g, '').toLowerCase()],
},
value: pool.share,
name: pool.name + ((isMobile() || this.widget) ? `` : ` (${pool.share}%)`),
label: {
overflow: 'none',
2023-01-03 05:24:14 -06:00
color: 'var(--tooltip-grey)',
alignTo: 'edge',
edgeDistance: edgeDistance,
},
tooltip: {
show: !isMobile() || !this.widget,
backgroundColor: 'rgba(17, 19, 31, 1)',
borderRadius: 4,
shadowColor: 'rgba(0, 0, 0, 0.5)',
textStyle: {
2023-01-03 05:24:14 -06:00
color: 'var(--tooltip-grey)',
},
borderColor: '#000',
formatter: () => {
2022-05-18 03:06:31 +04:00
const i = pool.blockCount.toString();
if (this.miningWindowPreference === '24h') {
return `<b style="color: white">${pool.name} (${pool.share}%)</b><br>` +
pool.lastEstimatedHashrate.toString() + ' ' + miningStats.miningUnits.hashrateUnit +
2023-03-13 17:40:17 +09:00
`<br>` + $localize`${ i }:INTERPOLATION: blocks`;
} else {
return `<b style="color: white">${pool.name} (${pool.share}%)</b><br>` +
2023-03-13 17:40:17 +09:00
$localize`${ i }:INTERPOLATION: blocks`;
}
}
},
2022-03-25 14:22:22 +09:00
data: pool.slug,
} as PieSeriesOption);
});
2024-04-04 21:55:14 +09:00
const percentage = totalShareOther.toFixed(2) + '%';
// 'Other'
data.push({
itemStyle: {
color: '#6b6b6b',
},
value: totalShareOther,
2024-04-04 21:55:14 +09:00
name: $localize`Other (${percentage})`,
label: {
overflow: 'none',
2023-01-03 05:24:14 -06:00
color: 'var(--tooltip-grey)',
alignTo: 'edge',
edgeDistance: edgeDistance
},
tooltip: {
backgroundColor: 'rgba(17, 19, 31, 1)',
borderRadius: 4,
shadowColor: 'rgba(0, 0, 0, 0.5)',
textStyle: {
2023-01-03 05:24:14 -06:00
color: 'var(--tooltip-grey)',
},
borderColor: '#000',
formatter: () => {
2023-03-13 17:40:17 +09:00
const i = totalBlockOther.toString();
if (this.miningWindowPreference === '24h') {
return `<b style="color: white">` + $localize`Other (${percentage})` + `</b><br>` +
totalEstimatedHashrateOther.toString() + ' ' + miningStats.miningUnits.hashrateUnit +
2023-03-13 17:40:17 +09:00
`<br>` + $localize`${ i }:INTERPOLATION: blocks`;
} else {
return `<b style="color: white">` + $localize`Other (${percentage})` + `</b><br>` +
2023-03-13 17:40:17 +09:00
$localize`${ i }:INTERPOLATION: blocks`;
}
}
},
data: 9999 as any,
} as PieSeriesOption);
return data;
}
2022-01-21 11:17:36 +09:00
prepareChartOptions(miningStats) {
let pieSize = ['20%', '80%']; // Desktop
if (isMobile() && !this.widget) {
pieSize = ['15%', '60%'];
}
this.chartOptions = {
animation: false,
2023-02-22 14:06:08 +09:00
color: chartColors.filter(color => color !== '#FDD835'),
tooltip: {
trigger: 'item',
textStyle: {
align: 'left',
}
},
series: [
{
2022-04-11 21:17:15 +09:00
zlevel: 0,
minShowLabelAngle: 1.8,
name: 'Mining pool',
type: 'pie',
radius: pieSize,
2022-01-21 11:17:36 +09:00
data: this.generatePoolsChartSerieData(miningStats),
labelLine: {
lineStyle: {
width: 2,
},
},
label: {
fontSize: 14,
formatter: (serie) => `${serie.name === 'Binance Pool' ? 'Binance\nPool' : serie.name}`,
},
itemStyle: {
2022-03-09 16:30:45 +01:00
borderRadius: 1,
borderWidth: 1,
borderColor: '#000',
},
emphasis: {
itemStyle: {
2022-02-17 17:52:12 +09:00
shadowBlur: 40,
shadowColor: 'rgba(0, 0, 0, 0.75)',
},
labelLine: {
lineStyle: {
width: 3,
}
}
}
}
2022-02-17 17:52:12 +09:00
],
};
}
onChartInit(ec) {
if (this.chartInstance !== undefined) {
return;
}
this.chartInstance = ec;
this.chartInstance.on('click', (e) => {
if (e.data.data === 9999) { // "Other"
return;
}
this.zone.run(() => {
const url = new RelativeUrlPipe(this.stateService).transform(`/mining/pool/${e.data.data}`);
this.router.navigate([url]);
});
});
}
2022-01-21 11:17:36 +09:00
/**
* Default mining stats if something goes wrong
*/
getEmptyMiningStat(): MiningStats {
2022-01-21 11:17:36 +09:00
return {
lastEstimatedHashrate: 'Error',
blockCount: 0,
totalEmptyBlock: 0,
totalEmptyBlockRatio: '',
pools: [],
totalBlockCount: 0,
2022-01-21 11:17:36 +09:00
miningUnits: {
hashrateDivider: 1,
hashrateUnit: '',
},
};
}
2022-05-05 16:18:28 +09:00
onSaveChart() {
const now = new Date();
2024-04-04 15:36:24 +09:00
this.chartOptions.backgroundColor = 'var(--active-bg)';
this.chartInstance.setOption(this.chartOptions);
2022-05-05 16:18:28 +09:00
download(this.chartInstance.getDataURL({
pixelRatio: 2,
excludeComponents: ['dataZoom'],
}), `pools-ranking-${this.timespan}-${Math.round(now.getTime() / 1000)}.svg`);
this.chartOptions.backgroundColor = 'none';
this.chartInstance.setOption(this.chartOptions);
2022-05-05 16:18:28 +09:00
}
isEllipsisActive(e) {
return (e.offsetWidth < e.scrollWidth);
}
}