diff --git a/frontend/src/app/components/block-fee-rates-graph/block-fee-rates-graph.component.html b/frontend/src/app/components/block-fee-rates-graph/block-fee-rates-graph.component.html
index 77c35cea8..f5eb5d1cf 100644
--- a/frontend/src/app/components/block-fee-rates-graph/block-fee-rates-graph.component.html
+++ b/frontend/src/app/components/block-fee-rates-graph/block-fee-rates-graph.component.html
@@ -1,13 +1,13 @@
-
+
-
-
+
+
+
+
\ No newline at end of file
diff --git a/frontend/src/app/components/block-fee-rates-graph/block-fee-rates-graph.component.scss b/frontend/src/app/components/block-fee-rates-graph/block-fee-rates-graph.component.scss
index f4f4dcc77..21dd458b5 100644
--- a/frontend/src/app/components/block-fee-rates-graph/block-fee-rates-graph.component.scss
+++ b/frontend/src/app/components/block-fee-rates-graph/block-fee-rates-graph.component.scss
@@ -57,7 +57,54 @@
.chart-widget {
width: 100%;
height: 100%;
- max-height: 270px;
+ max-height: 238px;
+}
+
+.block-fee-rates {
+ min-height: 56px;
+ display: block;
+ @media (min-width: 485px) {
+ display: flex;
+ flex-direction: row;
+ }
+ h5 {
+ margin-bottom: 10px;
+ }
+ .item {
+ width: 50%;
+ display: inline-block;
+ margin: 0px auto 20px;
+ &:nth-child(2) {
+ order: 2;
+ @media (min-width: 485px) {
+ order: 3;
+ }
+ }
+ &:nth-child(3) {
+ order: 3;
+ @media (min-width: 485px) {
+ order: 2;
+ display: block;
+ }
+ @media (min-width: 768px) {
+ display: none;
+ }
+ @media (min-width: 992px) {
+ display: block;
+ }
+ }
+ .card-title {
+ font-size: 1rem;
+ color: #4a68b9;
+ }
+ .card-text {
+ font-size: 18px;
+ span {
+ color: #ffffff66;
+ font-size: 12px;
+ }
+ }
+ }
}
.formRadioGroup {
@@ -85,6 +132,13 @@
}
}
+.skeleton-loader {
+ width: 100%;
+ display: block;
+ max-width: 80px;
+ margin: 15px auto 3px;
+}
+
.disabled {
pointer-events: none;
opacity: 0.5;
diff --git a/frontend/src/app/components/block-fee-rates-graph/block-fee-rates-graph.component.ts b/frontend/src/app/components/block-fee-rates-graph/block-fee-rates-graph.component.ts
index c4d061927..19c51527f 100644
--- a/frontend/src/app/components/block-fee-rates-graph/block-fee-rates-graph.component.ts
+++ b/frontend/src/app/components/block-fee-rates-graph/block-fee-rates-graph.component.ts
@@ -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,
diff --git a/frontend/src/app/services/api.service.ts b/frontend/src/app/services/api.service.ts
index 854d15c2a..e379a8626 100644
--- a/frontend/src/app/services/api.service.ts
+++ b/frontend/src/app/services/api.service.ts
@@ -1,7 +1,11 @@
import { Injectable } from '@angular/core';
import { HttpClient, HttpParams, HttpResponse } from '@angular/common/http';
import { CpfpInfo, OptimizedMempoolStats, AddressInformation, LiquidPegs, ITranslators,
+<<<<<<< HEAD
PoolStat, BlockExtended, TransactionStripped, RewardStats, AuditScore, BlockSizesAndWeights, RbfTree, BlockAudit, Acceleration, AccelerationHistoryParams } from '../interfaces/node-api.interface';
+=======
+ PoolStat, BlockExtended, TransactionStripped, RewardStats, AuditScore, BlockSizesAndWeights, RbfTree, BlockAudit, Acceleration } from '../interfaces/node-api.interface';
+>>>>>>> 9b9adcd43 (widgetify block fee rates chart)
import { BehaviorSubject, Observable, catchError, filter, of, shareReplay, take, tap } from 'rxjs';
import { StateService } from './state.service';
import { IBackendInfo, WebsocketResponse } from '../interfaces/websocket.interface';