widgetify block fee rates chart

This commit is contained in:
Mononaut
2023-07-20 16:25:09 +09:00
parent 4169e1053f
commit 42a3a380d5
4 changed files with 176 additions and 37 deletions

View File

@@ -1,6 +1,6 @@
import { ChangeDetectionStrategy, Component, Inject, Input, LOCALE_ID, NgZone, OnInit } from '@angular/core';
import { EChartsOption } from '../../graphs/echarts';
import { Observable, Subscription, combineLatest } from 'rxjs';
import { ChangeDetectionStrategy, ChangeDetectorRef, Component, Inject, Input, LOCALE_ID, NgZone, OnInit } from '@angular/core';
import { EChartsOption, graphic } from 'echarts';
import { Observable, Subscription, combineLatest, of } from 'rxjs';
import { map, share, startWith, switchMap, tap } from 'rxjs/operators';
import { ApiService } from '../../services/api.service';
import { SeoService } from '../../services/seo.service';
@@ -29,6 +29,7 @@ import { ActivatedRoute, Router } from '@angular/router';
changeDetection: ChangeDetectionStrategy.OnPush,
})
export class BlockFeeRatesGraphComponent implements OnInit {
@Input() widget = false;
@Input() right: number | string = 45;
@Input() left: number | string = 75;
@@ -57,39 +58,48 @@ export class BlockFeeRatesGraphComponent implements OnInit {
private router: Router,
private zone: NgZone,
private route: ActivatedRoute,
private cd: ChangeDetectorRef,
) {
this.radioGroupForm = this.formBuilder.group({ dateSpan: '1y' });
this.radioGroupForm.controls.dateSpan.setValue('1y');
}
ngOnInit(): void {
this.seoService.setTitle($localize`:@@ed8e33059967f554ff06b4f5b6049c465b92d9b3:Block Fee Rates`);
this.seoService.setDescription($localize`:@@meta.description.bitcoin.graphs.block-fee-rates:See Bitcoin feerates visualized over time, including minimum and maximum feerates per block along with feerates at various percentiles.`);
this.miningWindowPreference = this.miningService.getDefaultTimespan('24h');
if (this.widget) {
this.miningWindowPreference = '1m';
} else {
this.seoService.setTitle($localize`:@@ed8e33059967f554ff06b4f5b6049c465b92d9b3:Block Fee Rates`);
this.seoService.setDescription($localize`:@@meta.description.bitcoin.graphs.block-fee-rates:See Bitcoin feerates visualized over time, including minimum and maximum feerates per block along with feerates at various percentiles.`);
this.miningWindowPreference = this.miningService.getDefaultTimespan('24h');
}
this.radioGroupForm = this.formBuilder.group({ dateSpan: this.miningWindowPreference });
this.radioGroupForm.controls.dateSpan.setValue(this.miningWindowPreference);
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 });
}
});
if (!this.widget) {
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.statsObservable$ = combineLatest([
this.radioGroupForm.get('dateSpan').valueChanges.pipe(startWith(this.radioGroupForm.controls.dateSpan.value)),
this.widget ? of(this.miningWindowPreference) : this.radioGroupForm.get('dateSpan').valueChanges.pipe(startWith(this.radioGroupForm.controls.dateSpan.value)),
this.stateService.rateUnits$
]).pipe(
switchMap(([timespan, rateUnits]) => {
this.storageService.setValue('miningWindowPreference', timespan);
if (!this.widget) {
this.storageService.setValue('miningWindowPreference', timespan);
}
this.timespan = timespan;
this.isLoading = true;
return this.apiService.getHistoricalBlockFeeRates$(timespan)
.pipe(
tap((response) => {
// Group by percentile
const seriesData = {
const seriesData = this.widget ? { 'Median': [] } : {
'Min': [],
'10th': [],
'25th': [],
@@ -100,13 +110,17 @@ export class BlockFeeRatesGraphComponent implements OnInit {
};
for (const rate of response.body) {
const timestamp = rate.timestamp * 1000;
seriesData['Min'].push([timestamp, rate.avgFee_0, rate.avgHeight]);
seriesData['10th'].push([timestamp, rate.avgFee_10, rate.avgHeight]);
seriesData['25th'].push([timestamp, rate.avgFee_25, rate.avgHeight]);
seriesData['Median'].push([timestamp, rate.avgFee_50, rate.avgHeight]);
seriesData['75th'].push([timestamp, rate.avgFee_75, rate.avgHeight]);
seriesData['90th'].push([timestamp, rate.avgFee_90, rate.avgHeight]);
seriesData['Max'].push([timestamp, rate.avgFee_100, rate.avgHeight]);
if (this.widget) {
seriesData['Median'].push([timestamp, rate.avgFee_50, rate.avgHeight]);
} else {
seriesData['Min'].push([timestamp, rate.avgFee_0, rate.avgHeight]);
seriesData['10th'].push([timestamp, rate.avgFee_10, rate.avgHeight]);
seriesData['25th'].push([timestamp, rate.avgFee_25, rate.avgHeight]);
seriesData['Median'].push([timestamp, rate.avgFee_50, rate.avgHeight]);
seriesData['75th'].push([timestamp, rate.avgFee_75, rate.avgHeight]);
seriesData['90th'].push([timestamp, rate.avgFee_90, rate.avgHeight]);
seriesData['Max'].push([timestamp, rate.avgFee_100, rate.avgHeight]);
}
}
// Prepare chart
@@ -135,15 +149,42 @@ export class BlockFeeRatesGraphComponent implements OnInit {
});
}
if (this.widget) {
let maResolution = 30;
const medianMa = [];
for (let i = maResolution - 1; i < seriesData['Median'].length; ++i) {
let avg = 0;
for (let y = maResolution - 1; y >= 0; --y) {
avg += seriesData['Median'][i - y][1];
}
avg /= maResolution;
medianMa.push([seriesData['Median'][i][0], avg]);
}
series.push({
zlevel: 1,
name: 'MA',
data: medianMa,
type: 'line',
showSymbol: false,
symbol: 'none',
lineStyle: {
width: 3,
}
});
}
this.prepareChartOptions({
legends: legends,
series: series
}, rateUnits === 'wu');
this.isLoading = false;
this.cd.markForCheck();
}),
map((response) => {
return {
blockCount: parseInt(response.headers.get('x-total-count'), 10),
avgMedianRate: response.body.length ? response.body.reduce((acc, rate) => acc + rate.avgFee_50, 0) / response.body.length : 0,
};
}),
);
@@ -154,16 +195,22 @@ export class BlockFeeRatesGraphComponent implements OnInit {
prepareChartOptions(data, weightMode) {
this.chartOptions = {
color: ['#D81B60', '#8E24AA', '#1E88E5', '#7CB342', '#FDD835', '#6D4C41', '#546E7A'],
color: this.widget ? ['#6b6b6b', new graphic.LinearGradient(0, 0, 0, 0.65, [
{ offset: 0, color: '#F4511E' },
{ offset: 0.25, color: '#FB8C00' },
{ offset: 0.5, color: '#FFB300' },
{ offset: 0.75, color: '#FDD835' },
{ offset: 1, color: '#7CB342' }
])] : ['#D81B60', '#8E24AA', '#1E88E5', '#7CB342', '#FDD835', '#6D4C41', '#546E7A'],
animation: false,
grid: {
right: this.right,
left: this.left,
bottom: 80,
top: this.isMobile() ? 10 : 50,
bottom: this.widget ? 30 : 80,
top: this.widget ? 20 : (this.isMobile() ? 10 : 50),
},
tooltip: {
show: !this.isMobile(),
show: !this.isMobile() && !this.widget,
trigger: 'axis',
axisPointer: {
type: 'line'
@@ -201,7 +248,7 @@ export class BlockFeeRatesGraphComponent implements OnInit {
},
xAxis: data.series.length === 0 ? undefined :
{
name: formatterXAxisLabel(this.locale, this.timespan),
name: this.widget ? undefined : formatterXAxisLabel(this.locale, this.timespan),
nameLocation: 'middle',
nameTextStyle: {
padding: [10, 0, 0, 0],
@@ -218,7 +265,7 @@ export class BlockFeeRatesGraphComponent implements OnInit {
padding: [0, 5],
},
},
legend: (data.series.length === 0) ? undefined : {
legend: (this.widget || data.series.length === 0) ? undefined : {
padding: [10, 75],
data: data.legends,
selected: JSON.parse(this.storageService.getValue('fee_rates_legend')) ?? {
@@ -256,7 +303,7 @@ export class BlockFeeRatesGraphComponent implements OnInit {
max: (val) => this.timespan === 'all' ? Math.min(val.max, 5000) : undefined,
},
series: data.series,
dataZoom: [{
dataZoom: this.widget ? null : [{
type: 'inside',
realtime: true,
zoomLock: true,