2023-11-11 08:57:55 +00:00
|
|
|
import { HostListener, OnChanges, OnDestroy } from '@angular/core';
|
2021-12-15 23:53:12 +04:00
|
|
|
import { Component, Input, OnInit, ChangeDetectionStrategy } from '@angular/core';
|
2024-04-02 02:02:17 +00:00
|
|
|
import { TransactionStripped } from '../../interfaces/node-api.interface';
|
2023-03-15 11:43:18 +09:00
|
|
|
import { StateService } from '../../services/state.service';
|
|
|
|
import { VbytesPipe } from '../../shared/pipes/bytes-pipe/vbytes.pipe';
|
2023-06-30 19:41:12 -04:00
|
|
|
import { selectPowerOfTen } from '../../bitcoin.utils';
|
2023-06-15 18:56:34 -04:00
|
|
|
import { Subscription } from 'rxjs';
|
2021-12-15 23:53:12 +04:00
|
|
|
|
|
|
|
@Component({
|
|
|
|
selector: 'app-fee-distribution-graph',
|
|
|
|
templateUrl: './fee-distribution-graph.component.html',
|
2023-10-15 23:33:34 +00:00
|
|
|
styleUrls: ['./fee-distribution-graph.component.scss'],
|
2021-12-15 23:53:12 +04:00
|
|
|
changeDetection: ChangeDetectionStrategy.OnPush,
|
|
|
|
})
|
2023-06-15 18:56:34 -04:00
|
|
|
export class FeeDistributionGraphComponent implements OnInit, OnChanges, OnDestroy {
|
2023-06-07 13:22:27 -04:00
|
|
|
@Input() feeRange: number[];
|
|
|
|
@Input() vsize: number;
|
2023-03-15 11:43:18 +09:00
|
|
|
@Input() transactions: TransactionStripped[];
|
2021-12-15 23:53:12 +04:00
|
|
|
@Input() height: number | string = 210;
|
|
|
|
@Input() top: number | string = 20;
|
|
|
|
@Input() right: number | string = 22;
|
|
|
|
@Input() left: number | string = 30;
|
2023-03-15 11:43:18 +09:00
|
|
|
@Input() numSamples: number = 200;
|
|
|
|
@Input() numLabels: number = 10;
|
|
|
|
|
2023-06-07 13:22:27 -04:00
|
|
|
simple: boolean = false;
|
2023-03-15 11:43:18 +09:00
|
|
|
data: number[][];
|
|
|
|
labelInterval: number = 50;
|
2023-11-11 08:57:55 +00:00
|
|
|
smallScreen: boolean = window.innerWidth < 450;
|
2021-12-15 23:53:12 +04:00
|
|
|
|
2023-06-15 18:56:34 -04:00
|
|
|
rateUnitSub: Subscription;
|
|
|
|
weightMode: boolean = false;
|
2021-12-15 23:53:12 +04:00
|
|
|
mempoolVsizeFeesOptions: any;
|
|
|
|
mempoolVsizeFeesInitOptions = {
|
|
|
|
renderer: 'svg'
|
|
|
|
};
|
|
|
|
|
2023-03-15 11:43:18 +09:00
|
|
|
constructor(
|
2023-11-02 01:29:55 +00:00
|
|
|
public stateService: StateService,
|
2023-03-15 11:43:18 +09:00
|
|
|
private vbytesPipe: VbytesPipe,
|
|
|
|
) { }
|
2021-12-15 23:53:12 +04:00
|
|
|
|
2023-06-15 18:56:34 -04:00
|
|
|
ngOnInit() {
|
|
|
|
this.rateUnitSub = this.stateService.rateUnits$.subscribe(rateUnits => {
|
|
|
|
this.weightMode = rateUnits === 'wu';
|
|
|
|
if (this.data) {
|
|
|
|
this.mountChart();
|
|
|
|
}
|
|
|
|
});
|
2021-12-15 23:53:12 +04:00
|
|
|
}
|
|
|
|
|
2023-06-30 19:41:12 -04:00
|
|
|
ngOnChanges(): void {
|
2023-06-07 13:22:27 -04:00
|
|
|
this.simple = !!this.feeRange?.length;
|
2023-03-15 11:43:18 +09:00
|
|
|
this.prepareChart();
|
2021-12-15 23:53:12 +04:00
|
|
|
this.mountChart();
|
|
|
|
}
|
|
|
|
|
2023-06-30 19:41:12 -04:00
|
|
|
prepareChart(): void {
|
2023-06-07 13:22:27 -04:00
|
|
|
if (this.simple) {
|
|
|
|
this.data = this.feeRange.map((rate, index) => [index * 10, rate]);
|
|
|
|
this.labelInterval = 1;
|
|
|
|
return;
|
|
|
|
}
|
2023-03-15 11:43:18 +09:00
|
|
|
this.data = [];
|
|
|
|
if (!this.transactions?.length) {
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
const samples = [];
|
2024-03-31 05:40:51 +00:00
|
|
|
const txs = this.transactions.map(tx => { return { vsize: tx.vsize, rate: tx.rate || (tx.fee / tx.vsize) }; }).sort((a, b) => { return b.rate - a.rate; });
|
2023-03-15 11:43:18 +09:00
|
|
|
const maxBlockVSize = this.stateService.env.BLOCK_WEIGHT_UNITS / 4;
|
|
|
|
const sampleInterval = maxBlockVSize / this.numSamples;
|
|
|
|
let cumVSize = 0;
|
|
|
|
let sampleIndex = 0;
|
|
|
|
let nextSample = 0;
|
|
|
|
let txIndex = 0;
|
|
|
|
this.labelInterval = this.numSamples / this.numLabels;
|
|
|
|
while (nextSample <= maxBlockVSize) {
|
|
|
|
if (txIndex >= txs.length) {
|
2023-07-30 18:56:57 +09:00
|
|
|
samples.push([(1 - (sampleIndex / this.numSamples)) * 100, 0.000001]);
|
2023-03-15 11:43:18 +09:00
|
|
|
nextSample += sampleInterval;
|
|
|
|
sampleIndex++;
|
2023-06-12 11:54:58 -04:00
|
|
|
continue;
|
2023-03-15 11:43:18 +09:00
|
|
|
}
|
|
|
|
|
|
|
|
while (txs[txIndex] && nextSample < cumVSize + txs[txIndex].vsize) {
|
2023-07-30 18:56:57 +09:00
|
|
|
samples.push([(1 - (sampleIndex / this.numSamples)) * 100, txs[txIndex].rate || 0.000001]);
|
2023-03-15 11:43:18 +09:00
|
|
|
nextSample += sampleInterval;
|
|
|
|
sampleIndex++;
|
|
|
|
}
|
|
|
|
cumVSize += txs[txIndex].vsize;
|
|
|
|
txIndex++;
|
|
|
|
}
|
|
|
|
this.data = samples.reverse();
|
|
|
|
}
|
|
|
|
|
2023-06-30 19:41:12 -04:00
|
|
|
mountChart(): void {
|
2021-12-15 23:53:12 +04:00
|
|
|
this.mempoolVsizeFeesOptions = {
|
|
|
|
grid: {
|
|
|
|
height: '210',
|
2023-11-11 08:57:55 +00:00
|
|
|
right: this.smallScreen ? '10' : '20',
|
2021-12-15 23:53:12 +04:00
|
|
|
top: '22',
|
2023-11-11 08:57:55 +00:00
|
|
|
left: this.smallScreen ? '10' : '40',
|
2021-12-15 23:53:12 +04:00
|
|
|
},
|
|
|
|
xAxis: {
|
|
|
|
type: 'category',
|
|
|
|
boundaryGap: false,
|
2023-06-07 13:22:27 -04:00
|
|
|
name: '% Weight',
|
2023-03-15 11:43:18 +09:00
|
|
|
nameLocation: 'middle',
|
|
|
|
nameGap: 0,
|
|
|
|
nameTextStyle: {
|
|
|
|
verticalAlign: 'top',
|
|
|
|
padding: [30, 0, 0, 0],
|
|
|
|
},
|
|
|
|
axisLabel: {
|
|
|
|
interval: (index: number): boolean => { return index && (index % this.labelInterval === 0); },
|
2023-06-07 13:22:27 -04:00
|
|
|
formatter: (value: number): string => { return Number(value).toFixed(0); },
|
2023-03-15 11:43:18 +09:00
|
|
|
},
|
|
|
|
axisTick: {
|
|
|
|
interval: (index:number): boolean => { return (index % this.labelInterval === 0); },
|
|
|
|
},
|
2021-12-15 23:53:12 +04:00
|
|
|
},
|
|
|
|
yAxis: {
|
2023-07-30 18:56:57 +09:00
|
|
|
type: 'log',
|
|
|
|
min: 1,
|
|
|
|
max: this.data.reduce((min, val) => Math.max(min, val[1]), 1),
|
2023-03-15 11:43:18 +09:00
|
|
|
// name: 'Effective Fee Rate s/vb',
|
|
|
|
// nameLocation: 'middle',
|
2021-12-15 23:53:12 +04:00
|
|
|
splitLine: {
|
|
|
|
lineStyle: {
|
|
|
|
type: 'dotted',
|
2024-04-04 15:36:24 +09:00
|
|
|
color: 'var(--transparent-fg)',
|
2021-12-15 23:53:12 +04:00
|
|
|
opacity: 0.25,
|
|
|
|
}
|
2023-06-30 19:41:12 -04:00
|
|
|
},
|
|
|
|
axisLabel: {
|
2023-11-11 08:57:55 +00:00
|
|
|
show: !this.smallScreen,
|
2023-06-30 19:41:12 -04:00
|
|
|
formatter: (value: number): string => {
|
2023-06-15 18:56:34 -04:00
|
|
|
const unitValue = this.weightMode ? value / 4 : value;
|
|
|
|
const selectedPowerOfTen = selectPowerOfTen(unitValue);
|
2023-10-15 23:23:49 +00:00
|
|
|
const scaledValue = unitValue / selectedPowerOfTen.divider;
|
|
|
|
const newVal = scaledValue >= 100 ? Math.round(scaledValue) : scaledValue.toPrecision(3);
|
2023-06-30 19:41:12 -04:00
|
|
|
return `${newVal}${selectedPowerOfTen.unit}`;
|
|
|
|
},
|
2023-07-30 18:56:57 +09:00
|
|
|
},
|
|
|
|
axisTick: {
|
2023-11-11 08:57:55 +00:00
|
|
|
show: !this.smallScreen,
|
2021-12-15 23:53:12 +04:00
|
|
|
}
|
|
|
|
},
|
|
|
|
series: [{
|
|
|
|
data: this.data,
|
|
|
|
type: 'line',
|
|
|
|
label: {
|
|
|
|
show: true,
|
|
|
|
position: 'top',
|
|
|
|
color: '#ffffff',
|
|
|
|
textShadowBlur: 0,
|
2023-11-11 08:57:55 +00:00
|
|
|
fontSize: this.smallScreen ? 10 : 12,
|
2023-06-30 19:41:12 -04:00
|
|
|
formatter: (label: { data: number[] }): string => {
|
|
|
|
const value = label.data[1];
|
2023-06-15 18:56:34 -04:00
|
|
|
const unitValue = this.weightMode ? value / 4 : value;
|
|
|
|
const selectedPowerOfTen = selectPowerOfTen(unitValue);
|
2023-10-15 23:23:49 +00:00
|
|
|
const scaledValue = unitValue / selectedPowerOfTen.divider;
|
|
|
|
const newVal = scaledValue >= 100 ? Math.round(scaledValue) : scaledValue.toPrecision(3);
|
2023-06-30 19:41:12 -04:00
|
|
|
return `${newVal}${selectedPowerOfTen.unit}`;
|
2023-06-15 18:56:34 -04:00
|
|
|
}
|
2021-12-15 23:53:12 +04:00
|
|
|
},
|
2023-03-15 11:43:18 +09:00
|
|
|
showAllSymbol: false,
|
2021-12-15 23:53:12 +04:00
|
|
|
smooth: true,
|
|
|
|
lineStyle: {
|
|
|
|
color: '#D81B60',
|
2023-03-15 11:43:18 +09:00
|
|
|
width: 1,
|
2021-12-15 23:53:12 +04:00
|
|
|
},
|
|
|
|
itemStyle: {
|
|
|
|
color: '#b71c1c',
|
|
|
|
borderWidth: 10,
|
|
|
|
borderMiterLimit: 10,
|
|
|
|
opacity: 1,
|
|
|
|
},
|
|
|
|
areaStyle: {
|
|
|
|
color: '#D81B60',
|
|
|
|
opacity: 1,
|
|
|
|
}
|
|
|
|
}]
|
|
|
|
};
|
|
|
|
}
|
2023-06-15 18:56:34 -04:00
|
|
|
|
2023-11-11 08:57:55 +00:00
|
|
|
@HostListener('window:resize', ['$event'])
|
|
|
|
onResize(): void {
|
|
|
|
const isSmallScreen = window.innerWidth < 450;
|
|
|
|
if (this.smallScreen !== isSmallScreen) {
|
|
|
|
this.smallScreen = isSmallScreen;
|
|
|
|
this.prepareChart();
|
|
|
|
this.mountChart();
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2023-06-15 18:56:34 -04:00
|
|
|
ngOnDestroy(): void {
|
|
|
|
this.rateUnitSub.unsubscribe();
|
|
|
|
}
|
2021-12-15 23:53:12 +04:00
|
|
|
}
|