2022-05-05 16:38:16 +09:00
import { Component , OnInit , LOCALE_ID , Inject , ViewChild , ElementRef } from '@angular/core' ;
2020-02-16 22:15:07 +07:00
import { ActivatedRoute } from '@angular/router' ;
2022-11-28 11:55:23 +09:00
import { UntypedFormGroup , UntypedFormBuilder } from '@angular/forms' ;
2019-10-31 13:55:25 +08:00
import { of , merge } from 'rxjs' ;
2020-03-29 23:59:04 +07:00
import { switchMap } from 'rxjs/operators' ;
2020-02-16 22:15:07 +07:00
2020-02-17 00:26:57 +07:00
import { OptimizedMempoolStats } from '../../interfaces/node-api.interface' ;
2020-02-16 22:15:07 +07:00
import { WebsocketService } from '../../services/websocket.service' ;
import { ApiService } from '../../services/api.service' ;
2022-09-21 17:23:45 +02:00
import { StateService } from '../../services/state.service' ;
import { SeoService } from '../../services/seo.service' ;
import { StorageService } from '../../services/storage.service' ;
import { feeLevels , chartColors } from '../../app.constants' ;
2022-05-05 16:38:16 +09:00
import { MempoolGraphComponent } from '../mempool-graph/mempool-graph.component' ;
import { IncomingTransactionsGraphComponent } from '../incoming-transactions-graph/incoming-transactions-graph.component' ;
2019-07-21 17:59:47 +03:00
@Component ( {
selector : 'app-statistics' ,
templateUrl : './statistics.component.html' ,
styleUrls : [ './statistics.component.scss' ]
} )
export class StatisticsComponent implements OnInit {
2022-05-05 16:38:16 +09:00
@ViewChild ( 'mempoolgraph' ) mempoolGraph : MempoolGraphComponent ;
@ViewChild ( 'incominggraph' ) incomingGraph : IncomingTransactionsGraphComponent ;
2020-05-09 20:37:50 +07:00
network = '' ;
2020-03-28 19:24:13 +07:00
2019-07-21 17:59:47 +03:00
loading = true ;
spinnerLoading = false ;
2021-10-06 01:08:13 -03:00
feeLevels = feeLevels ;
chartColors = chartColors ;
2023-05-16 16:25:38 -06:00
filterSize = 100000 ;
2021-10-07 16:03:21 -03:00
filterFeeIndex = 1 ;
2023-10-20 15:10:40 +00:00
showCount = false ;
2023-05-16 16:25:38 -06:00
maxFeeIndex : number ;
2021-10-06 15:44:05 -03:00
dropDownOpen = false ;
2019-07-21 17:59:47 +03:00
2020-02-17 00:26:57 +07:00
mempoolStats : OptimizedMempoolStats [ ] = [ ] ;
2019-07-21 17:59:47 +03:00
mempoolVsizeFeesData : any ;
mempoolUnconfirmedTransactionsData : any ;
mempoolTransactionsWeightPerSecondData : any ;
2022-11-28 11:55:23 +09:00
radioGroupForm : UntypedFormGroup ;
2021-10-06 01:08:13 -03:00
graphWindowPreference : string ;
2021-09-26 11:41:55 -03:00
inverted : boolean ;
2022-01-04 04:42:19 +04:00
feeLevelDropdownData = [ ] ;
2022-05-05 16:38:16 +09:00
timespan = '' ;
2023-09-06 08:19:41 +09:00
titleCount = $localize ` Count ` ;
2019-07-21 17:59:47 +03:00
constructor (
@Inject ( LOCALE_ID ) private locale : string ,
2022-11-28 11:55:23 +09:00
private formBuilder : UntypedFormBuilder ,
2019-07-21 17:59:47 +03:00
private route : ActivatedRoute ,
2020-02-16 22:15:07 +07:00
private websocketService : WebsocketService ,
private apiService : ApiService ,
2022-03-16 17:28:00 +01:00
public stateService : StateService ,
2020-03-24 00:52:08 +07:00
private seoService : SeoService ,
2020-11-16 19:27:06 +07:00
private storageService : StorageService ,
2021-04-19 10:50:24 +04:00
) { }
2019-07-21 17:59:47 +03:00
ngOnInit() {
2021-09-26 11:41:55 -03:00
this . inverted = this . storageService . getValue ( 'inverted-graph' ) === 'true' ;
2022-01-04 04:42:19 +04:00
this . setFeeLevelDropdownData ( ) ;
2020-12-03 18:34:19 +07:00
this . seoService . setTitle ( $localize ` :@@5d4f792f048fcaa6df5948575d7cb325c9393383:Graphs ` ) ;
2023-08-30 20:26:07 +09:00
this . seoService . setDescription ( $localize ` :@@meta.description.bitcoin.graphs.mempool:See mempool size (in MvB) and transactions per second (in vB/s) visualized over time. ` ) ;
2020-05-09 20:37:50 +07:00
this . stateService . networkChanged $ . subscribe ( ( network ) = > this . network = network ) ;
2021-04-19 10:50:24 +04:00
this . graphWindowPreference = this . storageService . getValue ( 'graphWindowPreference' ) ? this . storageService . getValue ( 'graphWindowPreference' ) . trim ( ) : '2h' ;
2020-05-09 20:37:50 +07:00
2021-04-19 10:50:24 +04:00
this . radioGroupForm = this . formBuilder . group ( {
dateSpan : this.graphWindowPreference
} ) ;
2019-07-21 17:59:47 +03:00
this . route
. fragment
. subscribe ( ( fragment ) = > {
2023-06-19 12:46:10 -04:00
if ( [ '2h' , '24h' , '1w' , '1m' , '3m' , '6m' , '1y' , '2y' , '3y' , '4y' , 'all' ] . indexOf ( fragment ) > - 1 ) {
2020-02-16 22:15:07 +07:00
this . radioGroupForm . controls . dateSpan . setValue ( fragment , { emitEvent : false } ) ;
2023-06-19 12:46:10 -04:00
} else {
this . radioGroupForm . controls . dateSpan . setValue ( '2h' , { emitEvent : false } ) ;
2019-07-21 17:59:47 +03:00
}
} ) ;
merge (
of ( '' ) ,
2020-02-16 22:15:07 +07:00
this . radioGroupForm . controls . dateSpan . valueChanges
2019-07-21 17:59:47 +03:00
)
. pipe (
switchMap ( ( ) = > {
2022-05-05 16:38:16 +09:00
this . timespan = this . radioGroupForm . controls . dateSpan . value ;
2019-07-21 17:59:47 +03:00
this . spinnerLoading = true ;
2021-04-19 10:50:24 +04:00
if ( this . radioGroupForm . controls . dateSpan . value === '2h' ) {
2020-09-21 19:41:12 +07:00
this . websocketService . want ( [ 'blocks' , 'live-2h-chart' ] ) ;
2019-07-26 12:48:32 +03:00
return this . apiService . list2HStatistics $ ( ) ;
2019-07-21 17:59:47 +03:00
}
2020-09-21 19:41:12 +07:00
this . websocketService . want ( [ 'blocks' ] ) ;
2021-04-19 10:50:24 +04:00
if ( this . radioGroupForm . controls . dateSpan . value === '24h' ) {
2019-07-26 12:48:32 +03:00
return this . apiService . list24HStatistics $ ( ) ;
2019-07-21 17:59:47 +03:00
}
2021-04-19 10:50:24 +04:00
if ( this . radioGroupForm . controls . dateSpan . value === '1w' ) {
2019-07-21 17:59:47 +03:00
return this . apiService . list1WStatistics $ ( ) ;
}
2021-04-19 10:50:24 +04:00
if ( this . radioGroupForm . controls . dateSpan . value === '1m' ) {
2019-07-26 12:48:32 +03:00
return this . apiService . list1MStatistics $ ( ) ;
2019-07-21 17:59:47 +03:00
}
2021-04-19 10:50:24 +04:00
if ( this . radioGroupForm . controls . dateSpan . value === '3m' ) {
2019-07-26 12:48:32 +03:00
return this . apiService . list3MStatistics $ ( ) ;
2019-07-21 17:59:47 +03:00
}
2021-04-19 10:50:24 +04:00
if ( this . radioGroupForm . controls . dateSpan . value === '6m' ) {
2020-02-17 00:26:57 +07:00
return this . apiService . list6MStatistics $ ( ) ;
}
2021-11-01 22:06:10 -03:00
if ( this . radioGroupForm . controls . dateSpan . value === '1y' ) {
return this . apiService . list1YStatistics $ ( ) ;
}
if ( this . radioGroupForm . controls . dateSpan . value === '2y' ) {
return this . apiService . list2YStatistics $ ( ) ;
}
2023-03-04 18:48:16 +09:00
if ( this . radioGroupForm . controls . dateSpan . value === '3y' ) {
return this . apiService . list3YStatistics $ ( ) ;
}
2023-06-19 12:46:10 -04:00
if ( this . radioGroupForm . controls . dateSpan . value === '4y' ) {
return this . apiService . list4YStatistics $ ( ) ;
}
if ( this . radioGroupForm . controls . dateSpan . value === 'all' ) {
return this . apiService . listAllTimeStatistics $ ( ) ;
}
2019-07-21 17:59:47 +03:00
} )
)
2020-02-16 22:15:07 +07:00
. subscribe ( ( mempoolStats : any ) = > {
2019-07-26 12:48:32 +03:00
this . mempoolStats = mempoolStats ;
this . handleNewMempoolData ( this . mempoolStats . concat ( [ ] ) ) ;
2019-07-21 17:59:47 +03:00
this . loading = false ;
this . spinnerLoading = false ;
} ) ;
2019-07-26 12:48:32 +03:00
2020-02-16 22:15:07 +07:00
this . stateService . live2Chart $
2019-07-26 12:48:32 +03:00
. subscribe ( ( mempoolStats ) = > {
this . mempoolStats . unshift ( mempoolStats ) ;
this . mempoolStats = this . mempoolStats . slice ( 0 , this . mempoolStats . length - 1 ) ;
this . handleNewMempoolData ( this . mempoolStats . concat ( [ ] ) ) ;
} ) ;
2019-07-21 17:59:47 +03:00
}
2020-02-17 00:26:57 +07:00
handleNewMempoolData ( mempoolStats : OptimizedMempoolStats [ ] ) {
2019-07-21 17:59:47 +03:00
mempoolStats . reverse ( ) ;
const labels = mempoolStats . map ( stats = > stats . added ) ;
2023-05-16 16:25:38 -06:00
let maxTier = 0 ;
for ( let index = 37 ; index > - 1 ; index -- ) {
mempoolStats . forEach ( ( stats ) = > {
if ( stats . vsizes [ index ] >= this . filterSize ) {
maxTier = Math . max ( maxTier , index ) ;
}
} ) ;
}
this . maxFeeIndex = maxTier ;
2022-01-10 18:52:56 +09:00
this . capExtremeVbytesValues ( ) ;
2019-07-21 17:59:47 +03:00
this . mempoolTransactionsWeightPerSecondData = {
labels : labels ,
2021-12-11 10:38:13 +09:00
series : [ mempoolStats . map ( ( stats ) = > [ stats . added * 1000 , stats . vbytes_per_second ] ) ] ,
2019-07-21 17:59:47 +03:00
} ;
}
2020-11-16 19:27:06 +07:00
2021-04-18 23:17:16 -07:00
saveGraphPreference() {
this . storageService . setValue ( 'graphWindowPreference' , this . radioGroupForm . controls . dateSpan . value ) ;
}
2021-09-26 11:41:55 -03:00
invertGraph() {
this . storageService . setValue ( 'inverted-graph' , ! this . inverted ) ;
document . location . reload ( ) ;
}
2021-10-06 01:08:13 -03:00
2022-01-04 04:42:19 +04:00
setFeeLevelDropdownData() {
2023-05-16 16:25:38 -06:00
let _feeLevels = feeLevels ;
2022-01-04 04:42:19 +04:00
let _chartColors = chartColors ;
if ( ! this . inverted ) {
_feeLevels = [ . . . feeLevels ] . reverse ( ) ;
_chartColors = [ . . . chartColors ] . reverse ( ) ;
}
_feeLevels . forEach ( ( fee , i ) = > {
2023-05-16 16:25:38 -06:00
let range ;
const nextIndex = this . inverted ? i + 1 : i - 1 ;
if ( this . stateService . isLiquid ( ) ) {
if ( _feeLevels [ nextIndex ] == null ) {
range = ` ${ ( _feeLevels [ i ] / 10 ) . toFixed ( 1 ) } + ` ;
} else {
range = ` ${ ( _feeLevels [ i ] / 10 ) . toFixed ( 1 ) } - ${ ( _feeLevels [ nextIndex ] / 10 ) . toFixed ( 1 ) } ` ;
}
} else {
if ( _feeLevels [ nextIndex ] == null ) {
range = ` ${ _feeLevels [ i ] } + ` ;
} else {
range = ` ${ _feeLevels [ i ] } - ${ _feeLevels [ nextIndex ] } ` ;
}
}
2022-01-04 04:42:19 +04:00
if ( this . inverted ) {
this . feeLevelDropdownData . push ( {
fee : fee ,
2023-05-16 16:25:38 -06:00
range ,
2022-01-04 04:42:19 +04:00
color : _chartColors [ i ] ,
} ) ;
} else {
this . feeLevelDropdownData . push ( {
fee : fee ,
2023-05-16 16:25:38 -06:00
range ,
2023-06-05 14:23:37 -04:00
color : _chartColors [ i ] ,
2022-01-04 04:42:19 +04:00
} ) ;
}
2023-05-16 16:25:38 -06:00
} ) ;
2021-10-06 15:44:05 -03:00
}
2022-01-10 18:52:56 +09:00
/ * *
* All value higher that "median * capRatio" are capped
* /
capExtremeVbytesValues() {
2022-01-17 20:19:20 +09:00
if ( this . stateService . network . length !== 0 ) {
return ; // Only cap on Bitcoin mainnet
}
2022-01-10 18:52:56 +09:00
let capRatio = 10 ;
2023-03-04 18:48:16 +09:00
if ( [ '1m' , '3m' , '6m' , '1y' , '2y' , '3y' , '4y' ] . includes ( this . graphWindowPreference ) ) {
2022-01-10 18:52:56 +09:00
capRatio = 4 ;
}
// Find median value
2022-04-15 00:21:38 +09:00
const vBytes : number [ ] = [ ] ;
2022-01-17 20:19:20 +09:00
for ( const stat of this . mempoolStats ) {
vBytes . push ( stat . vbytes_per_second ) ;
2022-01-10 18:52:56 +09:00
}
const sorted = vBytes . slice ( ) . sort ( ( a , b ) = > a - b ) ;
const middle = Math . floor ( sorted . length / 2 ) ;
let median = sorted [ middle ] ;
if ( sorted . length % 2 === 0 ) {
median = ( sorted [ middle - 1 ] + sorted [ middle ] ) / 2 ;
}
// Cap
2022-01-17 20:19:20 +09:00
for ( const stat of this . mempoolStats ) {
stat . vbytes_per_second = Math . min ( median * capRatio , stat . vbytes_per_second ) ;
2022-01-10 18:52:56 +09:00
}
}
2022-05-05 16:38:16 +09:00
onSaveChart ( name ) {
if ( name === 'mempool' ) {
this . mempoolGraph . onSaveChart ( this . timespan ) ;
} else if ( name === 'incoming' ) {
this . incomingGraph . onSaveChart ( this . timespan ) ;
}
}
2022-07-16 21:00:32 +02:00
isMobile() {
return ( window . innerWidth <= 767.98 ) ;
}
2019-07-21 17:59:47 +03:00
}