Redesign mempool block fee distribution graph
This commit is contained in:
		
							parent
							
								
									5f787db30d
								
							
						
					
					
						commit
						e4f3642082
					
				| @ -1,5 +1,8 @@ | ||||
| import { OnChanges } from '@angular/core'; | ||||
| import { Component, Input, OnInit, ChangeDetectionStrategy } from '@angular/core'; | ||||
| import { TransactionStripped } from '../../interfaces/websocket.interface'; | ||||
| import { StateService } from '../../services/state.service'; | ||||
| import { VbytesPipe } from '../../shared/pipes/bytes-pipe/vbytes.pipe'; | ||||
| 
 | ||||
| @Component({ | ||||
|   selector: 'app-fee-distribution-graph', | ||||
| @ -7,41 +10,99 @@ import { Component, Input, OnInit, ChangeDetectionStrategy } from '@angular/core | ||||
|   changeDetection: ChangeDetectionStrategy.OnPush, | ||||
| }) | ||||
| export class FeeDistributionGraphComponent implements OnInit, OnChanges { | ||||
|   @Input() data: any; | ||||
|   @Input() transactions: TransactionStripped[]; | ||||
|   @Input() height: number | string = 210; | ||||
|   @Input() top: number | string = 20; | ||||
|   @Input() right: number | string = 22; | ||||
|   @Input() left: number | string = 30; | ||||
|   @Input() numSamples: number = 200; | ||||
|   @Input() numLabels: number = 10; | ||||
| 
 | ||||
|   data: number[][]; | ||||
|   labelInterval: number = 50; | ||||
| 
 | ||||
|   mempoolVsizeFeesOptions: any; | ||||
|   mempoolVsizeFeesInitOptions = { | ||||
|     renderer: 'svg' | ||||
|   }; | ||||
| 
 | ||||
|   constructor() { } | ||||
|   constructor( | ||||
|     private stateService: StateService, | ||||
|     private vbytesPipe: VbytesPipe, | ||||
|   ) { } | ||||
| 
 | ||||
|   ngOnInit() { | ||||
|     this.mountChart(); | ||||
|   } | ||||
| 
 | ||||
|   ngOnChanges() { | ||||
|     this.prepareChart(); | ||||
|     this.mountChart(); | ||||
|   } | ||||
| 
 | ||||
|   prepareChart() { | ||||
|     this.data = []; | ||||
|     if (!this.transactions?.length) { | ||||
|       return; | ||||
|     } | ||||
|     const samples = []; | ||||
|     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; }); | ||||
|     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) { | ||||
|         samples.push([1 - (sampleIndex / this.numSamples), 0]); | ||||
|         nextSample += sampleInterval; | ||||
|         sampleIndex++; | ||||
|         break; | ||||
|       } | ||||
| 
 | ||||
|       while (txs[txIndex] && nextSample < cumVSize + txs[txIndex].vsize) { | ||||
|         samples.push([1 - (sampleIndex / this.numSamples), txs[txIndex].rate]); | ||||
|         nextSample += sampleInterval; | ||||
|         sampleIndex++; | ||||
|       } | ||||
|       cumVSize += txs[txIndex].vsize; | ||||
|       txIndex++; | ||||
|     } | ||||
|     this.data = samples.reverse(); | ||||
|   } | ||||
| 
 | ||||
|   mountChart() { | ||||
|     this.mempoolVsizeFeesOptions = { | ||||
|       grid: { | ||||
|         height: '210', | ||||
|         right: '20', | ||||
|         top: '22', | ||||
|         left: '30', | ||||
|         left: '40', | ||||
|       }, | ||||
|       xAxis: { | ||||
|         type: 'category', | ||||
|         boundaryGap: false, | ||||
|         name: 'MvB', | ||||
|         nameLocation: 'middle', | ||||
|         nameGap: 0, | ||||
|         nameTextStyle: { | ||||
|           verticalAlign: 'top', | ||||
|           padding: [30, 0, 0, 0], | ||||
|         }, | ||||
|         axisLabel: { | ||||
|           interval: (index: number): boolean => { return index && (index % this.labelInterval === 0); }, | ||||
|           formatter: (value: number): string => { return Number(value).toFixed(1); }, | ||||
|         }, | ||||
|         axisTick: { | ||||
|           interval: (index:number): boolean => { return (index % this.labelInterval === 0); }, | ||||
|         }, | ||||
|       }, | ||||
|       yAxis: { | ||||
|         type: 'value', | ||||
|         // name: 'Effective Fee Rate s/vb',
 | ||||
|         // nameLocation: 'middle',
 | ||||
|         splitLine: { | ||||
|           lineStyle: { | ||||
|             type: 'dotted', | ||||
| @ -58,14 +119,13 @@ export class FeeDistributionGraphComponent implements OnInit, OnChanges { | ||||
|           position: 'top', | ||||
|           color: '#ffffff', | ||||
|           textShadowBlur: 0, | ||||
|           formatter: (label: any) => { | ||||
|             return Math.floor(label.data); | ||||
|           }, | ||||
|           formatter: (label: any): string => '' + Math.floor(label.data[1]), | ||||
|         }, | ||||
|         showAllSymbol: false, | ||||
|         smooth: true, | ||||
|         lineStyle: { | ||||
|           color: '#D81B60', | ||||
|           width: 4, | ||||
|           width: 1, | ||||
|         }, | ||||
|         itemStyle: { | ||||
|           color: '#b71c1c', | ||||
|  | ||||
| @ -39,11 +39,11 @@ | ||||
|             </tr> | ||||
|           </tbody> | ||||
|         </table> | ||||
|         <app-fee-distribution-graph *ngIf="webGlEnabled" [data]="mempoolBlock.feeRange" ></app-fee-distribution-graph> | ||||
|         <app-fee-distribution-graph *ngIf="webGlEnabled" [transactions]="mempoolBlockTransactions$ | async" ></app-fee-distribution-graph> | ||||
|       </div> | ||||
|       <div class="col-md chart-container"> | ||||
|         <app-mempool-block-overview *ngIf="webGlEnabled" [index]="mempoolBlockIndex" (txPreviewEvent)="setTxPreview($event)"></app-mempool-block-overview> | ||||
|         <app-fee-distribution-graph *ngIf="!webGlEnabled" [data]="mempoolBlock.feeRange" ></app-fee-distribution-graph> | ||||
|         <app-fee-distribution-graph *ngIf="!webGlEnabled" [transactions]="mempoolBlockTransactions$ | async" ></app-fee-distribution-graph> | ||||
|       </div> | ||||
|     </div> | ||||
|   </div> | ||||
|  | ||||
| @ -17,6 +17,7 @@ export class MempoolBlockComponent implements OnInit, OnDestroy { | ||||
|   network$: Observable<string>; | ||||
|   mempoolBlockIndex: number; | ||||
|   mempoolBlock$: Observable<MempoolBlock>; | ||||
|   mempoolBlockTransactions$: Observable<TransactionStripped[]>; | ||||
|   ordinal$: BehaviorSubject<string> = new BehaviorSubject(''); | ||||
|   previewTx: TransactionStripped | void; | ||||
|   webGlEnabled: boolean; | ||||
| @ -62,6 +63,8 @@ export class MempoolBlockComponent implements OnInit, OnDestroy { | ||||
|         }) | ||||
|       ); | ||||
| 
 | ||||
|     this.mempoolBlockTransactions$ = this.stateService.liveMempoolBlockTransactions$.pipe(map(txMap => Object.values(txMap))); | ||||
| 
 | ||||
|     this.network$ = this.stateService.networkChanged$; | ||||
|   } | ||||
| 
 | ||||
|  | ||||
| @ -1,11 +1,11 @@ | ||||
| import { Inject, Injectable, PLATFORM_ID, LOCALE_ID } from '@angular/core'; | ||||
| import { ReplaySubject, BehaviorSubject, Subject, fromEvent, Observable } from 'rxjs'; | ||||
| import { ReplaySubject, BehaviorSubject, Subject, fromEvent, Observable, merge } from 'rxjs'; | ||||
| import { Transaction } from '../interfaces/electrs.interface'; | ||||
| import { IBackendInfo, MempoolBlock, MempoolBlockWithTransactions, MempoolBlockDelta, MempoolInfo, Recommendedfees, ReplacedTransaction, TransactionStripped } from '../interfaces/websocket.interface'; | ||||
| import { BlockExtended, DifficultyAdjustment, MempoolPosition, OptimizedMempoolStats, RbfTree } from '../interfaces/node-api.interface'; | ||||
| import { Router, NavigationStart } from '@angular/router'; | ||||
| import { isPlatformBrowser } from '@angular/common'; | ||||
| import { map, shareReplay } from 'rxjs/operators'; | ||||
| import { map, scan, shareReplay, tap } from 'rxjs/operators'; | ||||
| import { StorageService } from './storage.service'; | ||||
| 
 | ||||
| interface MarkBlockState { | ||||
| @ -100,6 +100,7 @@ export class StateService { | ||||
|   mempoolBlocks$ = new ReplaySubject<MempoolBlock[]>(1); | ||||
|   mempoolBlockTransactions$ = new Subject<TransactionStripped[]>(); | ||||
|   mempoolBlockDelta$ = new Subject<MempoolBlockDelta>(); | ||||
|   liveMempoolBlockTransactions$: Observable<{ [txid: string]: TransactionStripped}>; | ||||
|   txReplaced$ = new Subject<ReplacedTransaction>(); | ||||
|   txRbfInfo$ = new Subject<RbfTree>(); | ||||
|   rbfLatest$ = new Subject<RbfTree[]>(); | ||||
| @ -166,6 +167,30 @@ export class StateService { | ||||
| 
 | ||||
|     this.blocks$ = new ReplaySubject<[BlockExtended, string]>(this.env.KEEP_BLOCKS_AMOUNT); | ||||
| 
 | ||||
|     this.liveMempoolBlockTransactions$ = merge( | ||||
|       this.mempoolBlockTransactions$.pipe(map(transactions => { return { transactions }; })), | ||||
|       this.mempoolBlockDelta$.pipe(map(delta => { return { delta }; })), | ||||
|     ).pipe(scan((transactions: { [txid: string]: TransactionStripped }, change: any): { [txid: string]: TransactionStripped } => { | ||||
|       if (change.transactions) { | ||||
|         const txMap = {} | ||||
|         change.transactions.forEach(tx => { | ||||
|           txMap[tx.txid] = tx; | ||||
|         }) | ||||
|         return txMap; | ||||
|       } else { | ||||
|         change.delta.changed.forEach(tx => { | ||||
|           transactions[tx.txid].rate = tx.rate; | ||||
|         }) | ||||
|         change.delta.removed.forEach(txid => { | ||||
|           delete transactions[txid]; | ||||
|         }); | ||||
|         change.delta.added.forEach(tx => { | ||||
|           transactions[tx.txid] = tx; | ||||
|         }); | ||||
|         return transactions; | ||||
|       } | ||||
|     }, {})); | ||||
| 
 | ||||
|     if (this.env.BASE_MODULE === 'bisq') { | ||||
|       this.network = this.env.BASE_MODULE; | ||||
|       this.networkChanged$.next(this.env.BASE_MODULE); | ||||
|  | ||||
| @ -500,7 +500,7 @@ html:lang(ru) .card-title { | ||||
| } | ||||
| 
 | ||||
| .fee-distribution-chart { | ||||
|   height: 250px; | ||||
|   height: 265px; | ||||
| } | ||||
| 
 | ||||
| .fees-wrapper-tooltip-chart { | ||||
|  | ||||
		Loading…
	
	
			
			x
			
			
		
	
		Reference in New Issue
	
	Block a user