2022-07-01 16:50:53 +02:00
import { Component , Inject , Input , LOCALE_ID , OnInit , HostBinding } from '@angular/core' ;
import { EChartsOption , graphic } from 'echarts' ;
import { Observable } from 'rxjs' ;
2022-08-18 15:09:03 +02:00
import { map , share , startWith , switchMap , tap } from 'rxjs/operators' ;
2022-09-21 17:23:45 +02:00
import { SeoService } from '../../services/seo.service' ;
2022-07-01 16:50:53 +02:00
import { formatNumber } from '@angular/common' ;
2022-11-28 11:55:23 +09:00
import { UntypedFormBuilder , UntypedFormGroup } from '@angular/forms' ;
2022-09-21 17:23:45 +02:00
import { StorageService } from '../../services/storage.service' ;
import { MiningService } from '../../services/mining.service' ;
import { download } from '../../shared/graphs.utils' ;
2022-07-01 16:50:53 +02:00
import { LightningApiService } from '../lightning-api.service' ;
2022-09-21 17:23:45 +02:00
import { AmountShortenerPipe } from '../../shared/pipes/amount-shortener.pipe' ;
import { isMobile } from '../../shared/common.utils' ;
2022-07-01 16:50:53 +02:00
@Component ( {
selector : 'app-lightning-statistics-chart' ,
templateUrl : './lightning-statistics-chart.component.html' ,
styleUrls : [ './lightning-statistics-chart.component.scss' ] ,
styles : [ `
. loadingGraphs {
position : absolute ;
top : 50 % ;
left : calc ( 50 % - 15 px ) ;
z - index : 100 ;
}
` ],
} )
export class LightningStatisticsChartComponent implements OnInit {
2022-07-06 13:20:37 +02:00
@Input ( ) right : number | string = 45 ;
2022-08-12 15:35:00 +02:00
@Input ( ) left : number | string = 45 ;
2022-07-01 16:50:53 +02:00
@Input ( ) widget = false ;
miningWindowPreference : string ;
2022-11-28 11:55:23 +09:00
radioGroupForm : UntypedFormGroup ;
2022-07-01 16:50:53 +02:00
chartOptions : EChartsOption = { } ;
chartInitOptions = {
renderer : 'svg' ,
} ;
@HostBinding ( 'attr.dir' ) dir = 'ltr' ;
2022-07-06 15:15:08 +02:00
capacityObservable$ : Observable < any > ;
2022-07-01 16:50:53 +02:00
isLoading = true ;
formatNumber = formatNumber ;
timespan = '' ;
chartInstance : any = undefined ;
constructor (
@Inject ( LOCALE_ID ) public locale : string ,
private seoService : SeoService ,
private lightningApiService : LightningApiService ,
2022-11-28 11:55:23 +09:00
private formBuilder : UntypedFormBuilder ,
2022-07-01 16:50:53 +02:00
private storageService : StorageService ,
private miningService : MiningService ,
2022-08-12 15:35:00 +02:00
private amountShortenerPipe : AmountShortenerPipe ,
2022-07-01 16:50:53 +02:00
) {
}
ngOnInit ( ) : void {
let firstRun = true ;
2022-07-06 15:15:08 +02:00
if ( this . widget ) {
2022-07-26 15:18:42 +02:00
this . miningWindowPreference = '3y' ;
2022-07-06 15:15:08 +02:00
} else {
2022-10-07 00:54:33 +04:00
this . seoService . setTitle ( $localize ` :@@ea8db27e6db64f8b940711948c001a1100e5fe9f:Lightning Network Capacity ` ) ;
2023-08-30 20:26:07 +09:00
this . seoService . setDescription ( $localize ` :@@meta.description.lightning.stats-chart:See the capacity of the Lightning network visualized over time in terms of the number of open channels and total bitcoin capacity. ` ) ;
2022-07-06 15:15:08 +02:00
this . miningWindowPreference = this . miningService . getDefaultTimespan ( 'all' ) ;
}
2022-07-01 16:50:53 +02:00
this . radioGroupForm = this . formBuilder . group ( { dateSpan : this.miningWindowPreference } ) ;
this . radioGroupForm . controls . dateSpan . setValue ( this . miningWindowPreference ) ;
2022-07-06 15:15:08 +02:00
this . capacityObservable $ = this . radioGroupForm . get ( 'dateSpan' ) . valueChanges
2022-07-01 16:50:53 +02:00
. pipe (
startWith ( this . miningWindowPreference ) ,
switchMap ( ( timespan ) = > {
this . timespan = timespan ;
2022-07-06 15:15:08 +02:00
if ( ! this . widget && ! firstRun ) {
this . storageService . setValue ( 'lightningWindowPreference' , timespan ) ;
2022-07-01 16:50:53 +02:00
}
firstRun = false ;
this . miningWindowPreference = timespan ;
this . isLoading = true ;
2022-07-06 15:15:08 +02:00
return this . lightningApiService . listStatistics $ ( timespan )
2022-07-01 16:50:53 +02:00
. pipe (
2022-07-06 15:15:08 +02:00
tap ( ( response ) = > {
const data = response . body ;
2022-07-01 16:50:53 +02:00
this . prepareChartOptions ( {
2022-07-06 13:20:37 +02:00
channel_count : data.map ( val = > [ val . added * 1000 , val . channel_count ] ) ,
2022-07-01 16:50:53 +02:00
capacity : data.map ( val = > [ val . added * 1000 , val . total_capacity ] ) ,
} ) ;
this . isLoading = false ;
} ) ,
2022-07-06 15:15:08 +02:00
map ( ( response ) = > {
return {
days : parseInt ( response . headers . get ( 'x-total-count' ) , 10 ) ,
} ;
} ) ,
2022-07-01 16:50:53 +02:00
) ;
} ) ,
2022-08-18 15:09:03 +02:00
share ( ) ,
) ;
2022-07-01 16:50:53 +02:00
}
2022-08-18 15:09:03 +02:00
prepareChartOptions ( data ) : void {
2022-07-01 16:50:53 +02:00
let title : object ;
2022-08-18 15:09:03 +02:00
if ( ! this . widget && data . channel_count . length === 0 ) {
2022-07-01 16:50:53 +02:00
title = {
textStyle : {
color : 'grey' ,
fontSize : 15
} ,
2022-12-25 23:17:04 +04:00
text : $localize ` Indexing in progress ` ,
2022-07-01 16:50:53 +02:00
left : 'center' ,
top : 'center'
} ;
2022-08-23 16:36:41 +02:00
} else if ( this . widget && data . channel_count . length > 0 ) {
2022-08-12 15:35:00 +02:00
title = {
textStyle : {
color : 'grey' ,
fontSize : 11
} ,
2022-10-07 00:54:33 +04:00
text : $localize ` :@@ea8db27e6db64f8b940711948c001a1100e5fe9f:Lightning Network Capacity ` ,
2022-08-12 15:35:00 +02:00
left : 'center' ,
2023-02-23 16:01:56 +09:00
top : 0 ,
2022-08-12 15:35:00 +02:00
zlevel : 10 ,
} ;
2022-07-01 16:50:53 +02:00
}
this . chartOptions = {
title : title ,
animation : false ,
color : [
2022-08-12 15:35:00 +02:00
'#FFB300' ,
new graphic . LinearGradient ( 0 , 0.75 , 0 , 1 , [
{ offset : 0 , color : '#D81B60' } ,
{ offset : 1 , color : '#D81B60AA' } ,
] ) ,
2022-07-01 16:50:53 +02:00
] ,
grid : {
2023-02-23 16:01:56 +09:00
height : this.widget ? 90 : undefined ,
top : this.widget ? 20 : 40 ,
2022-08-12 15:35:00 +02:00
bottom : this.widget ? 0 : 70 ,
2022-08-18 15:09:03 +02:00
right : ( isMobile ( ) && this . widget ) ? 35 : this.right ,
left : ( isMobile ( ) && this . widget ) ? 40 :this.left ,
2022-07-01 16:50:53 +02:00
} ,
tooltip : {
2022-08-18 15:09:03 +02:00
show : ! isMobile ( ) ,
2022-07-01 16:50:53 +02:00
trigger : 'axis' ,
axisPointer : {
type : 'line'
} ,
backgroundColor : 'rgba(17, 19, 31, 1)' ,
borderRadius : 4 ,
shadowColor : 'rgba(0, 0, 0, 0.5)' ,
textStyle : {
color : '#b1b1b1' ,
align : 'left' ,
} ,
borderColor : '#000' ,
2022-08-18 15:09:03 +02:00
formatter : ( ticks ) : string = > {
2022-07-01 16:50:53 +02:00
let sizeString = '' ;
let weightString = '' ;
for ( const tick of ticks ) {
2022-07-06 13:20:37 +02:00
if ( tick . seriesIndex === 0 ) { // Channels
2022-07-02 16:46:57 +02:00
sizeString = ` ${ tick . marker } ${ tick . seriesName } : ${ formatNumber ( tick . data [ 1 ] , this . locale , '1.0-0' ) } ` ;
2022-07-01 16:50:53 +02:00
} else if ( tick . seriesIndex === 1 ) { // Capacity
weightString = ` ${ tick . marker } ${ tick . seriesName } : ${ formatNumber ( tick . data [ 1 ] / 100000000 , this . locale , '1.0-0' ) } BTC ` ;
}
}
const date = new Date ( ticks [ 0 ] . data [ 0 ] ) . toLocaleDateString ( this . locale , { year : 'numeric' , month : 'short' , day : 'numeric' } ) ;
2022-08-18 15:09:03 +02:00
const tooltip = `
< b style = "color: white; margin-left: 18px" > $ { date } < / b > < br >
2022-07-01 16:50:53 +02:00
< span > $ { sizeString } < / span > < br >
2022-08-18 15:09:03 +02:00
< span > $ { weightString } < / span >
` ;
2022-07-01 16:50:53 +02:00
return tooltip ;
}
} ,
2022-07-06 13:20:37 +02:00
xAxis : data.channel_count.length === 0 ? undefined : {
2022-07-01 16:50:53 +02:00
type : 'time' ,
2022-08-18 15:09:03 +02:00
splitNumber : ( isMobile ( ) || this . widget ) ? 5 : 10 ,
2022-07-01 16:50:53 +02:00
axisLabel : {
hideOverlap : true ,
}
} ,
2022-08-12 15:35:00 +02:00
legend : this.widget || data . channel_count . length === 0 ? undefined : {
2022-07-01 16:50:53 +02:00
padding : 10 ,
data : [
{
2022-10-13 17:51:28 +04:00
name : $localize ` :@@807cf11e6ac1cde912496f764c176bdfdd6b7e19:Channels ` ,
2022-07-01 16:50:53 +02:00
inactiveColor : 'rgb(110, 112, 121)' ,
textStyle : {
color : 'white' ,
} ,
icon : 'roundRect' ,
} ,
{
2022-10-13 17:51:28 +04:00
name : $localize ` :@@ce9dfdc6dccb28dc75a78c704e09dc18fb02dcfa:Capacity ` ,
2022-07-01 16:50:53 +02:00
inactiveColor : 'rgb(110, 112, 121)' ,
textStyle : {
color : 'white' ,
} ,
icon : 'roundRect' ,
} ,
] ,
selected : JSON.parse ( this . storageService . getValue ( 'sizes_ln_legend' ) ) ? ? {
2022-07-06 13:20:37 +02:00
'Channels' : true ,
2022-08-12 15:35:00 +02:00
'Capacity' : true ,
2022-07-01 16:50:53 +02:00
}
} ,
2022-07-06 13:20:37 +02:00
yAxis : data.channel_count.length === 0 ? undefined : [
2022-07-01 16:50:53 +02:00
{
type : 'value' ,
axisLabel : {
color : 'rgb(110, 112, 121)' ,
2022-08-12 15:35:00 +02:00
formatter : ( val : number ) : string = > {
if ( this . widget ) {
return ` ${ this . amountShortenerPipe . transform ( val , 0 ) } ` ;
} else {
return ` ${ formatNumber ( Math . round ( val ) , this . locale , '1.0-0' ) } ` ;
}
2022-07-01 16:50:53 +02:00
}
} ,
splitLine : {
lineStyle : {
type : 'dotted' ,
color : '#ffffff66' ,
opacity : 0.25 ,
}
} ,
2022-08-12 15:35:00 +02:00
minInterval : this.widget ? 20000 : undefined ,
2022-07-01 16:50:53 +02:00
} ,
{
2022-07-06 13:20:37 +02:00
min : 0 ,
2022-07-01 16:50:53 +02:00
type : 'value' ,
position : 'right' ,
axisLabel : {
color : 'rgb(110, 112, 121)' ,
2022-08-12 15:35:00 +02:00
formatter : ( val : number ) : string = > {
if ( this . widget ) {
return ` ${ this . amountShortenerPipe . transform ( Math . round ( val / 100000000 ) , 0 ) } ` ;
} else {
return ` ${ formatNumber ( Math . round ( val / 100000000 ) , this . locale , '1.0-0' ) } ` ;
}
2022-07-01 16:50:53 +02:00
}
} ,
splitLine : {
show : false ,
}
}
] ,
2022-07-06 13:20:37 +02:00
series : data.channel_count.length === 0 ? [ ] : [
2022-07-01 16:50:53 +02:00
{
2022-07-02 16:46:57 +02:00
zlevel : 1 ,
2023-03-13 17:08:27 +09:00
name : $localize ` :@@807cf11e6ac1cde912496f764c176bdfdd6b7e19:Channels ` ,
2022-07-01 16:50:53 +02:00
showSymbol : false ,
symbol : 'none' ,
2022-07-06 13:20:37 +02:00
data : data.channel_count ,
2022-07-01 16:50:53 +02:00
type : 'line' ,
lineStyle : {
width : 2 ,
} ,
markLine : {
silent : true ,
symbol : 'none' ,
lineStyle : {
type : 'solid' ,
color : '#ffffff66' ,
opacity : 1 ,
width : 1 ,
} ,
2022-08-12 15:35:00 +02:00
} ,
2022-08-29 11:34:01 +02:00
smooth : false ,
2022-07-01 16:50:53 +02:00
} ,
{
2022-07-02 16:46:57 +02:00
zlevel : 0 ,
2022-07-01 16:50:53 +02:00
yAxisIndex : 1 ,
2022-10-13 17:51:28 +04:00
name : $localize ` :@@ce9dfdc6dccb28dc75a78c704e09dc18fb02dcfa:Capacity ` ,
2022-07-01 16:50:53 +02:00
showSymbol : false ,
symbol : 'none' ,
2022-07-02 16:46:57 +02:00
stack : 'Total' ,
2022-07-01 16:50:53 +02:00
data : data.capacity ,
2022-08-12 15:35:00 +02:00
areaStyle : {
opacity : 0.5 ,
} ,
2022-07-01 16:50:53 +02:00
type : 'line' ,
2022-08-29 11:34:01 +02:00
smooth : false ,
2022-07-01 16:50:53 +02:00
}
] ,
2022-08-12 15:35:00 +02:00
dataZoom : this.widget ? null : [ {
type : 'inside' ,
realtime : true ,
zoomLock : true ,
maxSpan : 100 ,
minSpan : 5 ,
moveOnMouseMove : false ,
} , {
showDetail : false ,
show : true ,
type : 'slider' ,
brushSelect : false ,
realtime : true ,
left : 20 ,
right : 15 ,
selectedDataBackground : {
lineStyle : {
color : '#fff' ,
opacity : 0.45 ,
} ,
areaStyle : {
opacity : 0 ,
}
} ,
} ] ,
2022-07-01 16:50:53 +02:00
} ;
}
2022-08-18 15:09:03 +02:00
onChartInit ( ec ) : void {
2022-07-01 16:50:53 +02:00
if ( this . chartInstance !== undefined ) {
return ;
}
this . chartInstance = ec ;
this . chartInstance . on ( 'legendselectchanged' , ( e ) = > {
this . storageService . setValue ( 'sizes_ln_legend' , JSON . stringify ( e . selected ) ) ;
} ) ;
}
2022-08-18 15:09:03 +02:00
onSaveChart ( ) : void {
2022-07-01 16:50:53 +02:00
// @ts-ignore
const prevBottom = this . chartOptions . grid . bottom ;
const now = new Date ( ) ;
// @ts-ignore
this . chartOptions . grid . bottom = 40 ;
this . chartOptions . backgroundColor = '#11131f' ;
this . chartInstance . setOption ( this . chartOptions ) ;
download ( this . chartInstance . getDataURL ( {
pixelRatio : 2 ,
2022-10-07 00:54:33 +04:00
} ) , ` lightning-network-capacity- ${ this . timespan } - ${ Math . round ( now . getTime ( ) / 1000 ) } .svg ` ) ;
2022-07-01 16:50:53 +02:00
// @ts-ignore
this . chartOptions . grid . bottom = prevBottom ;
this . chartOptions . backgroundColor = 'none' ;
this . chartInstance . setOption ( this . chartOptions ) ;
}
}